diff options
22 files changed, 12 insertions, 842 deletions
diff --git a/.changeset/poor-chicken-film.md b/.changeset/poor-chicken-film.md new file mode 100644 index 000000000..ecbc3b44c --- /dev/null +++ b/.changeset/poor-chicken-film.md @@ -0,0 +1,5 @@ +--- +'@astrojs/markdown-remark': major +--- + +Drop support for legacy Astro-flavored Markdown diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index 10e4cb8c0..34ed3647b 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -71,7 +71,6 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu const renderResult = await renderMarkdown(raw.content, { ...settings.config.markdown, fileURL: new URL(`file://${fileId}`), - isAstroFlavoredMd: false, isExperimentalContentCollections: settings.config.experimental.contentCollections, contentDir: getContentPaths(settings.config).contentDir, frontmatter: raw.data, diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json index 8e60a06c3..f8d2ec35e 100644 --- a/packages/markdown/remark/package.json +++ b/packages/markdown/remark/package.json @@ -25,19 +25,11 @@ "test": "mocha --exit --timeout 20000" }, "dependencies": { - "@astrojs/micromark-extension-mdx-jsx": "^1.0.3", "@astrojs/prism": "^1.0.0", "acorn": "^8.7.1", - "acorn-jsx": "^5.3.2", "github-slugger": "^1.4.0", "hast-util-to-html": "^8.0.3", "import-meta-resolve": "^2.1.0", - "mdast-util-from-markdown": "^1.2.0", - "mdast-util-mdx-expression": "^1.2.1", - "mdast-util-mdx-jsx": "^1.2.0", - "micromark-extension-mdx-expression": "^1.0.3", - "micromark-extension-mdx-md": "^1.0.0", - "micromark-util-combine-extensions": "^1.0.0", "rehype-raw": "^6.1.1", "rehype-stringify": "^9.0.3", "remark-gfm": "^3.0.1", @@ -46,7 +38,6 @@ "remark-smartypants": "^2.0.0", "shiki": "^0.11.1", "unified": "^10.1.2", - "unist-util-map": "^3.1.1", "unist-util-visit": "^4.1.0", "vfile": "^5.3.2" }, @@ -59,7 +50,6 @@ "@types/unist": "^2.0.6", "astro-scripts": "workspace:*", "chai": "^4.3.6", - "micromark-util-types": "^1.0.2", "mocha": "^9.2.2" } } diff --git a/packages/markdown/remark/src/index.ts b/packages/markdown/remark/src/index.ts index 24cffa287..91680f3fd 100644 --- a/packages/markdown/remark/src/index.ts +++ b/packages/markdown/remark/src/index.ts @@ -8,18 +8,10 @@ import type { import { toRemarkInitializeAstroData } from './frontmatter-injection.js'; import { loadPlugins } from './load-plugins.js'; import { rehypeHeadingIds } from './rehype-collect-headings.js'; -import rehypeEscape from './rehype-escape.js'; -import rehypeExpressions from './rehype-expressions.js'; -import rehypeIslands from './rehype-islands.js'; -import rehypeJsx from './rehype-jsx.js'; import toRemarkContentRelImageError from './remark-content-rel-image-error.js'; -import remarkEscape from './remark-escape.js'; -import remarkMarkAndUnravel from './remark-mark-and-unravel.js'; -import remarkMdxish from './remark-mdxish.js'; import remarkPrism from './remark-prism.js'; import scopedStyles from './remark-scoped-styles.js'; import remarkShiki from './remark-shiki.js'; -import remarkUnwrap from './remark-unwrap.js'; import rehypeRaw from 'rehype-raw'; import rehypeStringify from 'rehype-stringify'; @@ -61,7 +53,6 @@ export async function renderMarkdown( remarkRehype = markdownConfigDefaults.remarkRehype, gfm = markdownConfigDefaults.gfm, smartypants = markdownConfigDefaults.smartypants, - isAstroFlavoredMd = false, isExperimentalContentCollections = false, contentDir, frontmatter: userFrontmatter = {}, @@ -72,7 +63,7 @@ export async function renderMarkdown( let parser = unified() .use(markdown) .use(toRemarkInitializeAstroData({ userFrontmatter })) - .use(isAstroFlavoredMd ? [remarkMdxish, remarkMarkAndUnravel, remarkUnwrap, remarkEscape] : []); + .use([]); if (gfm) { parser.use(remarkGfm); @@ -109,15 +100,7 @@ export async function renderMarkdown( markdownToHtml as any, { allowDangerousHtml: true, - passThrough: isAstroFlavoredMd - ? [ - 'raw', - 'mdxFlowExpression', - 'mdxJsxFlowElement', - 'mdxJsxTextElement', - 'mdxTextExpression', - ] - : [], + passThrough: [], ...remarkRehype, }, ], @@ -127,13 +110,7 @@ export async function renderMarkdown( parser.use([[plugin, pluginOpts]]); }); - parser - .use( - isAstroFlavoredMd - ? [rehypeJsx, rehypeExpressions, rehypeEscape, rehypeIslands, rehypeHeadingIds] - : [rehypeHeadingIds, rehypeRaw] - ) - .use(rehypeStringify, { allowDangerousHtml: true }); + parser.use([rehypeHeadingIds, rehypeRaw]).use(rehypeStringify, { allowDangerousHtml: true }); let vfile: MarkdownVFile; try { diff --git a/packages/markdown/remark/src/mdast-util-mdxish.ts b/packages/markdown/remark/src/mdast-util-mdxish.ts deleted file mode 100644 index 10b19f5be..000000000 --- a/packages/markdown/remark/src/mdast-util-mdxish.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { mdxExpressionFromMarkdown, mdxExpressionToMarkdown } from 'mdast-util-mdx-expression'; -import { mdxJsxFromMarkdown, mdxJsxToMarkdown } from 'mdast-util-mdx-jsx'; - -export function mdxFromMarkdown(): any { - return [mdxExpressionFromMarkdown, mdxJsxFromMarkdown]; -} - -export function mdxToMarkdown(): any { - return { - extensions: [mdxExpressionToMarkdown, mdxJsxToMarkdown], - }; -} diff --git a/packages/markdown/remark/src/mdxjs.ts b/packages/markdown/remark/src/mdxjs.ts deleted file mode 100644 index c3a9849ea..000000000 --- a/packages/markdown/remark/src/mdxjs.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Note: The code in this file is based on `micromark-extension-mdxjs` -// and was adapted to use our fork `@astrojs/micromark-extension-mdx-jsx` -// instead of `micromark-extension-mdx-jsx` to allow some extended syntax. -// See `@astrojs/micromark-extension-mdx-jsx` on NPM for more details. -// Also, support for ESM imports & exports in Markdown content was removed. - -import { mdxJsx } from '@astrojs/micromark-extension-mdx-jsx'; -import { Parser } from 'acorn'; -import acornJsx from 'acorn-jsx'; -import type { Options } from 'micromark-extension-mdx-expression'; -import { mdxExpression } from 'micromark-extension-mdx-expression'; -import { mdxMd } from 'micromark-extension-mdx-md'; -import { combineExtensions } from 'micromark-util-combine-extensions'; -import type { Extension } from 'micromark-util-types'; - -export function mdxjs(options: Options): Extension { - const settings: any = Object.assign( - { - acorn: Parser.extend(acornJsx()), - acornOptions: { ecmaVersion: 2020, sourceType: 'module' }, - addResult: true, - }, - options - ); - - return combineExtensions([mdxExpression(settings), mdxJsx(settings), mdxMd]); -} diff --git a/packages/markdown/remark/src/rehype-collect-headings.ts b/packages/markdown/remark/src/rehype-collect-headings.ts index 03a3c6a23..142f796d6 100644 --- a/packages/markdown/remark/src/rehype-collect-headings.ts +++ b/packages/markdown/remark/src/rehype-collect-headings.ts @@ -21,7 +21,6 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> { const depth = Number.parseInt(level); let text = ''; - let isJSX = false; visit(node, (child, __, parent) => { if (child.type === 'element' || parent == null) { return; @@ -36,31 +35,17 @@ export function rehypeHeadingIds(): ReturnType<RehypePlugin> { text += child.value; } else { text += child.value.replace(/\{/g, '${'); - isJSX = isJSX || child.value.includes('{'); } } }); node.properties = node.properties || {}; if (typeof node.properties.id !== 'string') { - if (isJSX) { - // HACK: serialized JSX from internal plugins, ignore these for slug - const raw = toHtml(node.children, { allowDangerousHtml: true }) - .replace(/\n(<)/g, '<') - .replace(/(>)\n/g, '>'); - // HACK: for ids that have JSX content, use $$slug helper to generate slug at runtime - node.properties.id = `$$slug(\`${text}\`)`; - (node as any).type = 'raw'; - ( - node as any - ).value = `<${node.tagName} id={${node.properties.id}}>${raw}</${node.tagName}>`; - } else { - let slug = slugger.slug(text); + let slug = slugger.slug(text); - if (slug.endsWith('-')) slug = slug.slice(0, -1); + if (slug.endsWith('-')) slug = slug.slice(0, -1); - node.properties.id = slug; - } + node.properties.id = slug; } headings.push({ depth, slug: node.properties.id, text }); diff --git a/packages/markdown/remark/src/rehype-escape.ts b/packages/markdown/remark/src/rehype-escape.ts deleted file mode 100644 index a4cc32cf7..000000000 --- a/packages/markdown/remark/src/rehype-escape.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { SKIP, visit } from 'unist-util-visit'; - -export function escapeEntities(value: string): string { - return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); -} - -export default function rehypeEscape(): any { - return function (node: any): any { - return visit(node, 'element', (el) => { - if (el.tagName === 'code' || el.tagName === 'pre') { - el.properties['is:raw'] = true; - // Visit all raw children and escape HTML tags to prevent Markdown code - // like "This is a `<script>` tag" from actually opening a script tag - visit(el, 'raw', (raw) => { - raw.value = escapeEntities(raw.value); - }); - // Do not visit children to prevent double escaping - return SKIP; - } - }); - }; -} diff --git a/packages/markdown/remark/src/rehype-expressions.ts b/packages/markdown/remark/src/rehype-expressions.ts deleted file mode 100644 index f06f242e2..000000000 --- a/packages/markdown/remark/src/rehype-expressions.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { map } from 'unist-util-map'; - -export default function rehypeExpressions(): any { - return function (node: any): any { - return map(node, (child) => { - if (child.type === 'text') { - return { ...child, type: 'raw' }; - } - if (child.type === 'mdxTextExpression') { - return { type: 'raw', value: `{${(child as any).value}}` }; - } - if (child.type === 'mdxFlowExpression') { - return { type: 'raw', value: `{${(child as any).value}}` }; - } - return child; - }); - }; -} diff --git a/packages/markdown/remark/src/rehype-islands.ts b/packages/markdown/remark/src/rehype-islands.ts deleted file mode 100644 index a8b78848d..000000000 --- a/packages/markdown/remark/src/rehype-islands.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { SKIP, visit as _visit } from 'unist-util-visit'; - -// This is a workaround. -// It fixes a compatibility issue between different, incompatible ASTs given by plugins to Unist -const visit = _visit as ( - node: any, - type: string, - callback?: (node: any, index: number, parent: any) => any -) => any; - -// This fixes some confusing bugs coming from somewhere inside of our Markdown pipeline. -// `unist`/`remark`/`rehype` (not sure) often generate malformed HTML inside of <astro-island> -// For hydration to work properly, frameworks need the DOM to be the exact same on server/client. -// This reverts some "helpful corrections" that are applied to our perfectly valid HTML! -export default function rehypeIslands(): any { - return function (node: any): any { - return visit(node, 'element', (el) => { - // Bugs only happen inside of <astro-island> islands - if (el.tagName == 'astro-island') { - visit(el, 'text', (child, index, parent) => { - if (child.type === 'text') { - // Sometimes comments can be trapped as text, which causes them to be escaped - // This casts them back to real HTML comments - if (parent && child.value.indexOf('<!--') > -1 && index != null) { - parent.children.splice(index, 1, { - ...child, - type: 'comment', - value: child.value.replace('<!--', '').replace('-->', '').trim(), - }); - return [SKIP, index]; - } - // For some reason `rehype` likes to inject extra linebreaks, - // but React and Vue throw hydration errors when they see these! - // This removes any extra linebreaks, which is fine because - // framework compilers don't preserve them anyway - child.value = child.value.replace(/\n+/g, ''); - return child; - } - }); - } - }); - }; -} diff --git a/packages/markdown/remark/src/rehype-jsx.ts b/packages/markdown/remark/src/rehype-jsx.ts deleted file mode 100644 index 7082997e2..000000000 --- a/packages/markdown/remark/src/rehype-jsx.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { visit } from 'unist-util-visit'; -import type { RehypePlugin } from './types.js'; - -const MDX_ELEMENTS = ['mdxJsxFlowElement', 'mdxJsxTextElement']; - -export default function rehypeJsx(): ReturnType<RehypePlugin> { - return function (tree) { - visit(tree, MDX_ELEMENTS, (node: any, index: number | null, parent: any) => { - if (index === null || !Boolean(parent)) return; - - const attrs = node.attributes.reduce((acc: any[], entry: any) => { - let attr = entry.value; - if (attr && typeof attr === 'object') { - attr = `{${attr.value}}`; - } else if (attr && entry.type === 'mdxJsxExpressionAttribute') { - attr = `{${attr}}`; - } else if (attr === null) { - attr = ''; - } else if (typeof attr === 'string') { - attr = `"${attr}"`; - } - if (!entry.name) { - return acc + ` ${attr}`; - } - return acc + ` ${entry.name}${attr ? '=' : ''}${attr}`; - }, ''); - - if (node.children.length === 0) { - node.type = 'raw'; - node.value = `<${node.name}${attrs} />`; - return; - } - - // If the current node is a JSX <a> element, remove autolinks from its children - // to prevent Markdown code like `<a href="/">**Go to www.example.com now!**</a>` - // from creating a nested link to `www.example.com` - if (node.name === 'a') { - visit(node, 'element', (el, elIndex, elParent) => { - const isAutolink = - el.tagName === 'a' && - el.children.length === 1 && - el.children[0].type === 'text' && - el.children[0].value.match(/^(https?:\/\/|www\.)/i); - - // If we found an autolink, remove it by replacing it with its text-only child - if (isAutolink) { - elParent.children.splice(elIndex, 1, el.children[0]); - } - }); - } - - // Replace the current node with its children - // wrapped by raw opening and closing tags - const openingTag = { - type: 'raw', - value: `<${node.name}${attrs}>`, - }; - const closingTag = { - type: 'raw', - value: `</${node.name}>`, - }; - parent.children.splice(index, 1, openingTag, ...node.children, closingTag); - }); - }; -} diff --git a/packages/markdown/remark/src/remark-escape.ts b/packages/markdown/remark/src/remark-escape.ts deleted file mode 100644 index 84cdf2efa..000000000 --- a/packages/markdown/remark/src/remark-escape.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Literal } from 'unist'; -import { visit } from 'unist-util-visit'; - -// In code blocks, this removes the JS comment wrapper added to -// HTML comments by vite-plugin-markdown-legacy. -export default function remarkEscape() { - return (tree: any) => { - visit(tree, 'code', removeCommentWrapper); - visit(tree, 'inlineCode', removeCommentWrapper); - }; - - function removeCommentWrapper(node: Literal<string>) { - node.value = node.value.replace(/{\/\*<!--/gs, '<!--').replace(/-->\*\/}/gs, '-->'); - } -} diff --git a/packages/markdown/remark/src/remark-mark-and-unravel.ts b/packages/markdown/remark/src/remark-mark-and-unravel.ts deleted file mode 100644 index 294d4211d..000000000 --- a/packages/markdown/remark/src/remark-mark-and-unravel.ts +++ /dev/null @@ -1,72 +0,0 @@ -// https://github.com/mdx-js/mdx/blob/main/packages/mdx/lib/plugin/remark-mark-and-unravel.js -/** - * @typedef {import('mdast').Root} Root - * @typedef {import('mdast').Content} Content - * @typedef {Root|Content} Node - * @typedef {Extract<Node, import('unist').Parent>} Parent - * - * @typedef {import('remark-mdx')} DoNotTouchAsThisImportItIncludesMdxInTree - */ - -import { visit } from 'unist-util-visit'; - -/** - * A tiny plugin that unravels `<p><h1>x</h1></p>` but also - * `<p><Component /></p>` (so it has no knowledge of “HTML”). - * It also marks JSX as being explicitly JSX, so when a user passes a `h1` - * component, it is used for `# heading` but not for `<h1>heading</h1>`. - * - * @type {import('unified').Plugin<Array<void>, Root>} - */ -export default function remarkMarkAndUnravel() { - return (tree: any) => { - visit(tree, (node, index, parent_) => { - const parent = /** @type {Parent} */ parent_; - let offset = -1; - let all = true; - /** @type {boolean|undefined} */ - let oneOrMore; - - if (parent && typeof index === 'number' && node.type === 'paragraph') { - const children = node.children; - - while (++offset < children.length) { - const child = children[offset]; - - if (child.type === 'mdxJsxTextElement' || child.type === 'mdxTextExpression') { - oneOrMore = true; - } else if (child.type === 'text' && /^[\t\r\n ]+$/.test(String(child.value))) { - // Empty. - } else { - all = false; - break; - } - } - - if (all && oneOrMore) { - offset = -1; - - while (++offset < children.length) { - const child = children[offset]; - - if (child.type === 'mdxJsxTextElement') { - child.type = 'mdxJsxFlowElement'; - } - - if (child.type === 'mdxTextExpression') { - child.type = 'mdxFlowExpression'; - } - } - - parent.children.splice(index, 1, ...children); - return index; - } - } - - if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') { - const data = node.data || (node.data = {}); - data._mdxExplicitJsx = true; - } - }); - }; -} diff --git a/packages/markdown/remark/src/remark-mdxish.ts b/packages/markdown/remark/src/remark-mdxish.ts deleted file mode 100644 index 0f70f87b3..000000000 --- a/packages/markdown/remark/src/remark-mdxish.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type * as fromMarkdown from 'mdast-util-from-markdown'; -import type { Tag } from 'mdast-util-mdx-jsx'; -import { mdxFromMarkdown, mdxToMarkdown } from './mdast-util-mdxish.js'; -import { mdxjs } from './mdxjs.js'; - -// Prepare markdown extensions once to prevent performance issues -const extMdxJs = mdxjs({}); -const extMdxFromMarkdown = makeFromMarkdownLessStrict(mdxFromMarkdown()); -const extMdxToMarkdown = mdxToMarkdown(); - -export default function remarkMdxish(this: any) { - const data = this.data(); - - add('micromarkExtensions', extMdxJs); - add('fromMarkdownExtensions', extMdxFromMarkdown); - add('toMarkdownExtensions', extMdxToMarkdown); - - function add(field: string, value: unknown) { - const list = data[field] ? data[field] : (data[field] = []); - list.push(value); - } -} - -function makeFromMarkdownLessStrict(extensions: fromMarkdown.Extension[]) { - extensions.forEach((extension) => { - // Fix exit handlers that are too strict - ['mdxJsxFlowTag', 'mdxJsxTextTag'].forEach((exitHandler) => { - if (!extension.exit || !extension.exit[exitHandler]) return; - extension.exit[exitHandler] = chainHandlers(fixSelfClosing, extension.exit[exitHandler]); - }); - }); - - return extensions; -} - -const selfClosingTags = new Set([ - 'area', - 'base', - 'br', - 'col', - 'embed', - 'hr', - 'img', - 'input', - 'link', - 'meta', - 'source', - 'track', - 'wbr', -]); - -function fixSelfClosing(this: fromMarkdown.CompileContext) { - const tag = this.getData('mdxJsxTag') as Tag; - if (tag.name && selfClosingTags.has(tag.name)) tag.selfClosing = true; -} - -function chainHandlers(...handlers: fromMarkdown.Handle[]) { - return function handlerChain(this: fromMarkdown.CompileContext, token: fromMarkdown.Token) { - handlers.forEach((handler) => handler.call(this, token)); - }; -} diff --git a/packages/markdown/remark/src/remark-unwrap.ts b/packages/markdown/remark/src/remark-unwrap.ts deleted file mode 100644 index 399bd6cd6..000000000 --- a/packages/markdown/remark/src/remark-unwrap.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { SKIP, visit as _visit } from 'unist-util-visit'; - -// This is a workaround. -// It fixes a compatibility issue between different, incompatible ASTs given by plugins to Unist -const visit = _visit as ( - node: any, - type: string, - callback?: (node: any, index: number, parent: any) => any -) => any; - -// Remove the wrapping paragraph for <astro-island> islands -export default function remarkUnwrap() { - const astroRootNodes = new Set(); - let insideAstroRoot = false; - - return (tree: any) => { - // reset state - insideAstroRoot = false; - astroRootNodes.clear(); - - visit(tree, 'html', (node) => { - if (node.value.indexOf('<astro-island') > -1 && !insideAstroRoot) { - insideAstroRoot = true; - } - if (node.value.indexOf('</astro-island') > -1 && insideAstroRoot) { - insideAstroRoot = false; - } - astroRootNodes.add(node); - }); - - visit(tree, 'paragraph', (node, index, parent) => { - if (parent && typeof index === 'number' && containsAstroRootNode(node)) { - parent.children.splice(index, 1, ...node.children); - return [SKIP, index]; - } - }); - }; - - function containsAstroRootNode(node: any) { - return node.children - .map((child: any) => astroRootNodes.has(child)) - .reduce((all: boolean, v: boolean) => (all ? all : v), false); - } -} diff --git a/packages/markdown/remark/src/types.ts b/packages/markdown/remark/src/types.ts index d5133aaf0..40b2ac841 100644 --- a/packages/markdown/remark/src/types.ts +++ b/packages/markdown/remark/src/types.ts @@ -58,7 +58,6 @@ export interface MarkdownRenderingOptions extends AstroMarkdownOptions { $?: { scopedClassName: string | null; }; - isAstroFlavoredMd?: boolean; /** Used to prevent relative image imports from `src/content/` */ isExperimentalContentCollections?: boolean; /** Used to prevent relative image imports from `src/content/` */ diff --git a/packages/markdown/remark/test/autolinking.test.js b/packages/markdown/remark/test/autolinking.test.js index c5cd64657..48bf894be 100644 --- a/packages/markdown/remark/test/autolinking.test.js +++ b/packages/markdown/remark/test/autolinking.test.js @@ -33,76 +33,4 @@ describe('autolinking', () => { ); }); }); - - describe('astro-flavored md', () => { - const renderAstroMd = (text) => renderMarkdown(text, { isAstroFlavoredMd: true }); - - it('does not autolink URLs in code blocks', async () => { - const { code } = await renderAstroMd( - 'See `https://example.com` or `www.example.com` for more.', - {} - ); - - chai - .expect(code.trim()) - .to.equal( - `<p>See <code is:raw>https://example.com</code> or ` + - `<code is:raw>www.example.com</code> for more.</p>` - ); - }); - - it('does not autolink URLs in fenced code blocks', async () => { - const { code } = await renderAstroMd( - 'Example:\n```\nGo to https://example.com or www.example.com now.\n```' - ); - - chai - .expect(code) - .to.contain(`<pre is:raw`) - .to.contain(`Go to https://example.com or www.example.com now.`); - }); - - it('does not autolink URLs starting with a protocol when nested inside links', async () => { - const { code } = await renderAstroMd( - `See [http://example.com](http://example.com) or ` + - `<a test href="https://example.com">https://example.com</a>` - ); - - chai - .expect(code.replace(/\n/g, '')) - .to.equal( - `<p>See <a href="http://example.com">http://example.com</a> or ` + - `<a test href="https://example.com">https://example.com</a></p>` - ); - }); - - it('does not autolink URLs starting with "www." when nested inside links', async () => { - const { code } = await renderAstroMd( - `See [www.example.com](https://www.example.com) or ` + - `<a test href="https://www.example.com">www.example.com</a>` - ); - - chai - .expect(code.replace(/\n/g, '')) - .to.equal( - `<p>See <a href="https://www.example.com">www.example.com</a> or ` + - `<a test href="https://www.example.com">www.example.com</a></p>` - ); - }); - - it('does not autolink URLs when nested several layers deep inside links', async () => { - const { code } = await renderAstroMd( - `<a href="https://www.example.com">**Visit _our www.example.com or ` + - `http://localhost pages_ for more!**</a>` - ); - - chai - .expect(code.replace(/\n/g, '')) - .to.equal( - `<a href="https://www.example.com"><strong>` + - `Visit <em>our www.example.com or http://localhost pages</em> for more!` + - `</strong></a>` - ); - }); - }); }); diff --git a/packages/markdown/remark/test/components.test.js b/packages/markdown/remark/test/components.test.js deleted file mode 100644 index e60469426..000000000 --- a/packages/markdown/remark/test/components.test.js +++ /dev/null @@ -1,78 +0,0 @@ -import { renderMarkdown } from '../dist/index.js'; -import chai from 'chai'; - -describe('components', () => { - const renderAstroMd = (text) => renderMarkdown(text, { isAstroFlavoredMd: true }); - - it('should be able to serialize string', async () => { - const { code } = await renderAstroMd(`<Component str="cool!" />`); - - chai.expect(code).to.equal(`<Component str="cool!" />`); - }); - - it('should be able to serialize boolean attribute', async () => { - const { code } = await renderAstroMd(`<Component bool={true} />`); - - chai.expect(code).to.equal(`<Component bool={true} />`); - }); - - it('should be able to serialize array', async () => { - const { code } = await renderAstroMd(`<Component prop={["a", "b", "c"]} />`); - - chai.expect(code).to.equal(`<Component prop={["a", "b", "c"]} />`); - }); - - it('should be able to serialize object', async () => { - const { code } = await renderAstroMd(`<Component prop={{ a: 0, b: 1, c: 2 }} />`); - - chai.expect(code).to.equal(`<Component prop={{ a: 0, b: 1, c: 2 }} />`); - }); - - it('should be able to serialize empty attribute', async () => { - const { code } = await renderAstroMd(`<Component empty />`); - - chai.expect(code).to.equal(`<Component empty />`); - }); - - // Notable omission: shorthand attribute - - it('should be able to serialize spread attribute', async () => { - const { code } = await renderAstroMd(`<Component {...spread} />`); - - chai.expect(code).to.equal(`<Component {...spread} />`); - }); - - it('should allow client:* directives', async () => { - const { code } = await renderAstroMd(`<Component client:load />`); - - chai.expect(code).to.equal(`<Component client:load />`); - }); - - it('should normalize children', async () => { - const { code } = await renderAstroMd(`<Component bool={true}>Hello world!</Component>`); - - chai.expect(code).to.equal(`<Component bool={true}>Hello world!</Component>`); - }); - - it('should be able to nest components', async () => { - const { code } = await renderAstroMd( - `<Component bool={true}><Component>Hello world!</Component></Component>`, - {} - ); - - chai - .expect(code) - .to.equal(`<Component bool={true}><Component>Hello world!</Component></Component>`); - }); - - it('should allow markdown without many spaces', async () => { - const { code } = await renderAstroMd( - `<Component> -# Hello world! -</Component>`, - {} - ); - - chai.expect(code).to.equal(`<Component><h1 id="hello-world">Hello world!</h1></Component>`); - }); -}); diff --git a/packages/markdown/remark/test/entities.test.js b/packages/markdown/remark/test/entities.test.js index b7d551d72..d35ed3a89 100644 --- a/packages/markdown/remark/test/entities.test.js +++ b/packages/markdown/remark/test/entities.test.js @@ -3,21 +3,8 @@ import { expect } from 'chai'; describe('entities', () => { it('should not unescape entities in regular Markdown', async () => { - const { code } = await renderMarkdown(`<i>This should NOT be italic</i>`, { - isAstroFlavoredMd: false, - }); + const { code } = await renderMarkdown(`<i>This should NOT be italic</i>`, {}); expect(code).to.equal(`<p><i>This should NOT be italic</i></p>`); }); - - it('should not escape entities in code blocks twice in Astro-flavored markdown', async () => { - const { code } = await renderMarkdown(`\`\`\`astro\n<h1>{x && x.name || ''}!</h1>\n\`\`\``, { - isAstroFlavoredMd: true, - syntaxHighlight: false, - }); - - expect(code).to.equal( - `<pre is:raw><code class="language-astro"><h1>{x && x.name || ''}!</h1>\n</code></pre>` - ); - }); }); diff --git a/packages/markdown/remark/test/expressions.test.js b/packages/markdown/remark/test/expressions.test.js deleted file mode 100644 index b2cb1a73d..000000000 --- a/packages/markdown/remark/test/expressions.test.js +++ /dev/null @@ -1,125 +0,0 @@ -import { renderMarkdown } from '../dist/index.js'; -import chai from 'chai'; - -describe('expressions', () => { - const renderAstroMd = (text, opts) => renderMarkdown(text, { isAstroFlavoredMd: true, ...opts }); - - it('should be able to serialize bare expression', async () => { - const { code } = await renderAstroMd(`{a}`, {}); - - chai.expect(code).to.equal(`{a}`); - }); - - it('should be able to serialize expression inside component', async () => { - const { code } = await renderAstroMd(`<Component>{a}</Component>`, {}); - - chai.expect(code).to.equal(`<Component>{a}</Component>`); - }); - - it('should be able to serialize expression inside markdown', async () => { - const { code } = await renderAstroMd(`# {frontmatter.title}`, {}); - - chai - .expect(code) - .to.equal(`<h1 id={$$slug(\`\${frontmatter.title}\`)}>{frontmatter.title}</h1>`); - }); - - it('should be able to serialize complex expression inside markdown', async () => { - const { code } = await renderAstroMd(`# Hello {frontmatter.name}`, {}); - - chai - .expect(code) - .to.equal(`<h1 id={$$slug(\`Hello \${frontmatter.name}\`)}>Hello {frontmatter.name}</h1>`); - }); - - it('should be able to serialize complex expression with markup inside markdown', async () => { - const { code } = await renderAstroMd(`# Hello <span>{frontmatter.name}</span>`, {}); - - chai - .expect(code) - .to.equal( - `<h1 id={$$slug(\`Hello \${frontmatter.name}\`)}>Hello <span>{frontmatter.name}</span></h1>` - ); - }); - - it('should be able to avoid evaluating JSX-like expressions in an inline code & generate a slug for id', async () => { - const { code } = await renderAstroMd(`# \`{frontmatter.title}\``, {}); - - chai - .expect(code) - .to.equal('<h1 id="frontmattertitle"><code is:raw>{frontmatter.title}</code></h1>'); - }); - - it('should be able to avoid evaluating JSX-like expressions in inline codes', async () => { - const { code } = await renderAstroMd(`# \`{ foo }\` is a shorthand for \`{ foo: foo }\``, {}); - - chai - .expect(code) - .to.equal( - '<h1 id="-foo--is-a-shorthand-for--foo-foo"><code is:raw>{ foo }</code> is a shorthand for <code is:raw>{ foo: foo }</code></h1>' - ); - }); - - it('should be able to avoid evaluating JSX-like expressions & escape HTML tag characters in inline codes', async () => { - const { code } = await renderAstroMd( - `###### \`{}\` is equivalent to \`Record<never, never>\` <small>(at TypeScript v{frontmatter.version})</small>`, - {} - ); - - chai - .expect(code) - .to.equal( - `<h6 id={$$slug(\`{} is equivalent to Record<never, never> (at TypeScript v\${frontmatter.version})\`)}><code is:raw>{}</code> is equivalent to <code is:raw>Record<never, never></code> <small>(at TypeScript v{frontmatter.version})</small></h6>` - ); - }); - - it('should be able to encode ampersand characters in code blocks', async () => { - const { code } = await renderAstroMd( - 'The ampersand in ` ` must be encoded in code blocks.', - {} - ); - - chai - .expect(code) - .to.equal( - '<p>The ampersand in <code is:raw>&nbsp;</code> must be encoded in code blocks.</p>' - ); - }); - - it('should be able to encode ampersand characters in fenced code blocks', async () => { - const { code } = await renderAstroMd(` - \`\`\`md - The ampersand in \` \` must be encoded in code blocks. - \`\`\` - `); - - chai.expect(code).to.match(/^<pre is:raw.*<code>.*The ampersand in `&nbsp;`/); - }); - - it('should be able to serialize function expression', async () => { - const { code } = await renderAstroMd( - `{frontmatter.list.map(item => <p id={item}>{item}</p>)}`, - {} - ); - - chai.expect(code).to.equal(`{frontmatter.list.map(item => <p id={item}>{item}</p>)}`); - }); - - it('should unwrap HTML comments in inline code blocks', async () => { - const { code } = await renderAstroMd(`\`{/*<!-- HTML comment -->*/}\``); - - chai.expect(code).to.equal('<p><code is:raw><!-- HTML comment --></code></p>'); - }); - - it('should unwrap HTML comments in code fences', async () => { - const { code } = await renderAstroMd( - ` - \`\`\` - <!-- HTML comment --> - \`\`\` - ` - ); - - chai.expect(code).to.match(/(?<!{\/\*)<!-- HTML comment -->(?!\*\/})/); - }); -}); diff --git a/packages/markdown/remark/test/strictness.test.js b/packages/markdown/remark/test/strictness.test.js deleted file mode 100644 index 324415c6a..000000000 --- a/packages/markdown/remark/test/strictness.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import { renderMarkdown } from '../dist/index.js'; -import chai from 'chai'; - -describe('strictness in Astro-flavored markdown', () => { - const renderAstroMd = (text, opts) => renderMarkdown(text, { isAstroFlavoredMd: true, ...opts }); - - it('should allow self-closing HTML tags (void elements)', async () => { - const { code } = await renderAstroMd( - `Use self-closing void elements<br>like word<wbr>break and images: <img src="hi.jpg">`, - {} - ); - - chai - .expect(code) - .to.equal( - `<p>Use self-closing void elements<br />like word<wbr />break and images: ` + - `<img src="hi.jpg" /></p>` - ); - }); - - it('should allow attribute names starting with ":" after element names', async () => { - const { code } = await renderAstroMd(`<div :class="open ? '' : 'hidden'">Test</div>`, {}); - - chai.expect(code.trim()).to.equal(`<div :class="open ? '' : 'hidden'">Test</div>`); - }); - - it('should allow attribute names starting with ":" after local element names', async () => { - const { code } = await renderAstroMd(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`, {}); - - chai.expect(code.trim()).to.equal(`<div.abc :class="open ? '' : 'hidden'">x</div.abc>`); - }); - - it('should allow attribute names starting with ":" after attribute names', async () => { - const { code } = await renderAstroMd(`<input type="text" disabled :placeholder="hi">`, {}); - - chai.expect(code.trim()).to.equal(`<input type="text" disabled :placeholder="hi" />`); - }); - - it('should allow attribute names starting with ":" after local attribute names', async () => { - const { code } = await renderAstroMd( - `<input type="text" x-test:disabled :placeholder="hi">`, - {} - ); - - chai.expect(code.trim()).to.equal(`<input type="text" x-test:disabled :placeholder="hi" />`); - }); - - it('should allow attribute names starting with ":" after attribute values', async () => { - const { code } = await renderAstroMd(`<input type="text" :placeholder="placeholder">`, {}); - - chai.expect(code.trim()).to.equal(`<input type="text" :placeholder="placeholder" />`); - }); - - it('should allow attribute names starting with "@" after element names', async () => { - const { code } = await renderAstroMd(`<button @click="handleClick">Test</button>`, {}); - - chai.expect(code.trim()).to.equal(`<button @click="handleClick">Test</button>`); - }); - - it('should allow attribute names starting with "@" after local element names', async () => { - const { code } = await renderAstroMd( - `<button.local @click="handleClick">Test</button.local>`, - {} - ); - - chai.expect(code.trim()).to.equal(`<button.local @click="handleClick">Test</button.local>`); - }); - - it('should allow attribute names starting with "@" after attribute names', async () => { - const { code } = await renderAstroMd(`<button disabled @click="handleClick">Test</button>`, {}); - - chai.expect(code.trim()).to.equal(`<button disabled @click="handleClick">Test</button>`); - }); - - it('should allow attribute names starting with "@" after local attribute names', async () => { - const { code } = await renderAstroMd( - `<button x-test:disabled @click="handleClick">Test</button>`, - {} - ); - - chai.expect(code.trim()).to.equal(`<button x-test:disabled @click="handleClick">Test</button>`); - }); - - it('should allow attribute names starting with "@" after attribute values', async () => { - const { code } = await renderAstroMd( - `<button type="submit" @click="handleClick">Test</button>`, - {} - ); - - chai.expect(code.trim()).to.equal(`<button type="submit" @click="handleClick">Test</button>`); - }); - - it('should allow attribute names containing dots', async () => { - const { code } = await renderAstroMd(`<input x-on:input.debounce.500ms="fetchResults">`, {}); - - chai.expect(code.trim()).to.equal(`<input x-on:input.debounce.500ms="fetchResults" />`); - }); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43cb19094..64e0f0b3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3462,7 +3462,6 @@ importers: packages/markdown/remark: specifiers: - '@astrojs/micromark-extension-mdx-jsx': ^1.0.3 '@astrojs/prism': ^1.0.0 '@types/chai': ^4.3.1 '@types/github-slugger': ^1.3.0 @@ -3471,19 +3470,11 @@ importers: '@types/mocha': ^9.1.1 '@types/unist': ^2.0.6 acorn: ^8.7.1 - acorn-jsx: ^5.3.2 astro-scripts: workspace:* chai: ^4.3.6 github-slugger: ^1.4.0 hast-util-to-html: ^8.0.3 import-meta-resolve: ^2.1.0 - mdast-util-from-markdown: ^1.2.0 - mdast-util-mdx-expression: ^1.2.1 - mdast-util-mdx-jsx: ^1.2.0 - micromark-extension-mdx-expression: ^1.0.3 - micromark-extension-mdx-md: ^1.0.0 - micromark-util-combine-extensions: ^1.0.0 - micromark-util-types: ^1.0.2 mocha: ^9.2.2 rehype-raw: ^6.1.1 rehype-stringify: ^9.0.3 @@ -3493,23 +3484,14 @@ importers: remark-smartypants: ^2.0.0 shiki: ^0.11.1 unified: ^10.1.2 - unist-util-map: ^3.1.1 unist-util-visit: ^4.1.0 vfile: ^5.3.2 dependencies: - '@astrojs/micromark-extension-mdx-jsx': 1.0.3 '@astrojs/prism': link:../../astro-prism acorn: 8.8.1 - acorn-jsx: 5.3.2_acorn@8.8.1 github-slugger: 1.5.0 hast-util-to-html: 8.0.3 import-meta-resolve: 2.2.1 - mdast-util-from-markdown: 1.2.0 - mdast-util-mdx-expression: 1.3.1 - mdast-util-mdx-jsx: 1.2.0 - micromark-extension-mdx-expression: 1.0.3 - micromark-extension-mdx-md: 1.0.0 - micromark-util-combine-extensions: 1.0.0 rehype-raw: 6.1.1 rehype-stringify: 9.0.3 remark-gfm: 3.0.1 @@ -3518,7 +3500,6 @@ importers: remark-smartypants: 2.0.0 shiki: 0.11.1 unified: 10.1.2 - unist-util-map: 3.1.2 unist-util-visit: 4.1.1 vfile: 5.3.6 devDependencies: @@ -3530,7 +3511,6 @@ importers: '@types/unist': 2.0.6 astro-scripts: link:../../../scripts chai: 4.3.7 - micromark-util-types: 1.0.2 mocha: 9.2.2 packages/telemetry: |