From f7cf7c1973b44859b76cb48bff984786e04032b9 Mon Sep 17 00:00:00 2001 From: Antony Faris Date: Wed, 24 Nov 2021 14:58:45 -0500 Subject: [PATCH] Format markdown component content (#63) --- .changeset/quick-emus-whisper.md | 5 + src/printer.ts | 96 +++++-------------- test/astro-prettier.test.ts | 13 ++- .../markdown-component-content/input.astro | 47 +++++++++ .../markdown-component-content/output.astro | 53 ++++++++++ 5 files changed, 140 insertions(+), 74 deletions(-) create mode 100644 .changeset/quick-emus-whisper.md create mode 100644 test/fixtures/markdown-component-content/input.astro create mode 100644 test/fixtures/markdown-component-content/output.astro diff --git a/.changeset/quick-emus-whisper.md b/.changeset/quick-emus-whisper.md new file mode 100644 index 0000000..3021c43 --- /dev/null +++ b/.changeset/quick-emus-whisper.md @@ -0,0 +1,5 @@ +--- +'prettier-plugin-astro': patch +--- + +Format markdown component content diff --git a/src/printer.ts b/src/printer.ts index 0bbe7f8..3aff6a9 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -22,7 +22,6 @@ import { getMarkdownName, getText, getUnencodedText, - indent as manualIndent, isASTNode, isDocCommand, isEmptyDoc, @@ -76,12 +75,6 @@ function printTopLevelParts(node: Ast, path: AstPath, opts: ParserOptions, print } } - // remove trailing softline, if any - // const lastDoc = docs[docs.length - 1]; - // const lastItem = lastDoc[lastDoc.length - 1]; - - // if (lastItem.type === 'line') docs[docs.length - 1].pop(); - return join(softline, docs); } @@ -133,17 +126,6 @@ function print(path: AstPath, opts: ParserOptions, print: printFn): Doc { return printTopLevelParts(node, path, opts, print); } - // switch (true) { - // case !node: - // return ''; - // case typeof node === 'string': - // return node; - // case Array.isArray(node): - // return path.map((childPath) => childPath.call(print)); - // case isASTNode(node): - // return printTopLevelParts(node, path, opts, print); - // } - // 2. attach comments shallowly to children, if any (https://prettier.io/docs/en/plugins.html#manually-attaching-a-comment) if (!isPreTagContent(path) && !isMarkdownSubDoc) { attachCommentsHTML(node); @@ -159,11 +141,6 @@ function print(path: AstPath, opts: ParserOptions, print: printFn): Doc { if (!isNodeWithChildren(node) || node.children.every(isEmptyTextNode)) return ''; - // If this is the start of a markdown code block, remove arbitrary beginning whitespace - if (isMarkdownSubDoc) { - if (isEmptyTextNode(node.children[0])) node.children.shift(); - } - // If we don't see any JSX expressions, this is just embedded HTML // and we can skip a bunch of work. Hooray! const hasInlineComponent = node.children.filter((x) => x.type === 'InlineComponent').length > 0; @@ -231,10 +208,6 @@ function print(path: AstPath, opts: ParserOptions, print: printFn): Doc { return fill(splitTextToDocs(node)); } - // case 'SlotTemplate': - // case 'Window': - // case 'Head': - // case 'Title': case 'Element': case 'InlineComponent': case 'Slot': { @@ -284,28 +257,12 @@ function print(path: AstPath, opts: ParserOptions, print: printFn): Doc { let body; - const isMarkdownComponent = - // TODO: opts.__astro & opts.__astro.markdownName - // @ts-ignore - node.type === 'InlineComponent' && opts.__astro && opts.__astro.markdownName && opts.__astro.markdownName.has(node.name) && isNodeWithChildren(node); - if (isEmpty) { body = isInlineElement(path, opts, node) && node.children.length && isTextNodeStartingWithWhitespace(node.children[0]) && !isPreTagContent(path) ? () => line : // () => (opts.jsxBracketNewLine ? '' : softline); () => softline; - } else if (isMarkdownComponent) { - // collapse children into raw Markdown text - // TODO: CHECK TYPE - // @ts-ignore - const text = node.children.map(getUnencodedText).join('').trim(); - node.children = [{ start: firstChild.start, end: lastChild.end - 2, type: 'Text', data: text, raw: text, __isRawMarkdown: true }]; - body = () => path.map(print, 'children'); - - // set hugEnd - hugStart = false; - hugEnd = false; } else if (isPreTagContent(path)) { body = () => printRaw(node, opts.originalText); } else if (isInlineElement(path, opts, node) && !isPreTagContent(path)) { @@ -328,9 +285,6 @@ function print(path: AstPath, opts: ParserOptions, print: printFn): Doc { if (isPreTagContent(path)) { noHugSeparatorStart = ''; noHugSeparatorEnd = ''; - } else if (isMarkdownComponent) { - noHugSeparatorStart = softline; - noHugSeparatorEnd = softline; } else { let didSetEndSeparator = false; @@ -463,6 +417,8 @@ function expressionParser(text: string, parsers: any, opts: ParserOptions) { return { ...ast, program: ast.program.body[0].expression }; } +let markdownComponentName = new Set(); + function embed(path: AstPath, print: printFn, textToDoc: (text: string, options: object) => Doc, opts: ParserOptions) { // TODO: ADD TYPES OR FIND ANOTHER WAY TO ACHIVE THIS // @ts-ignore @@ -472,23 +428,6 @@ function embed(path: AstPath, print: printFn, textToDoc: (text: string, options: if (!node) return null; - // TODO: ADD TYPES OR FIND ANOTHER WAY TO ACHIVE THIS - // @ts-ignore - if (node.__isRawMarkdown) { - // TODO: CHECK TYPE - // @ts-ignore - const docs = textToDoc(getUnencodedText(node), { ...opts, parser: 'markdown' }); - return stripTrailingHardline(docs); - } - - // TODO: ADD TYPES OR FIND ANOTHER WAY TO ACHIVE THIS - // @ts-ignore - if (node.type === 'Script' && !opts.__astro.markdownName) { - // TODO: ADD TYPES OR FIND ANOTHER WAY TO ACHIVE THIS - // @ts-ignore - opts.__astro.markdownName = getMarkdownName(node.content); - } - // TODO: ADD TYPES OR FIND ANOTHER WAY TO ACHIVE THIS // @ts-ignore if (node.isJS) { @@ -514,6 +453,7 @@ function embed(path: AstPath, print: printFn, textToDoc: (text: string, options: } if (node.type === 'Script' && node.context === 'setup') { + markdownComponentName = getMarkdownName(node.content); return group(['---', hardline, textToDoc(node.content, { ...opts, parser: 'typescript' }), '---', hardline]); } @@ -523,11 +463,7 @@ function embed(path: AstPath, print: printFn, textToDoc: (text: string, options: if (parent && parent.type === 'Element' && parent.name === 'script') { const formatttedScript = textToDoc(node.data, { ...opts, parser: 'typescript' }); - if (typeof formatttedScript === 'string') return formatttedScript; - if (isDocCommand(formatttedScript)) return formatttedScript; - return group(formatttedScript[0]); - // const [formatttedScript, ,] = textToDoc(node.data, { ...opts, parser: 'typescript' }); - // return group(formatttedScript); + return stripTrailingHardline(formatttedScript); } } @@ -559,9 +495,8 @@ function embed(path: AstPath, print: printFn, textToDoc: (text: string, options: // the css parser appends an extra indented hardline, which we want outside of the `indent()`, // so we remove the last element of the array let formattedStyles = textToDoc(styleTagContent, { ...opts, parser: parserLang }); - if (typeof formattedStyles === 'string') return formattedStyles; - if (isDocCommand(formattedStyles)) return formattedStyles; - formattedStyles = formattedStyles[0]; + + formattedStyles = stripTrailingHardline(formattedStyles); // print const attributes = path.map(print, 'attributes'); @@ -591,6 +526,25 @@ function embed(path: AstPath, print: printFn, textToDoc: (text: string, options: } } + // MARKDOWN COMPONENT + if (node.type === 'InlineComponent' && markdownComponentName.has(node.name)) { + let content = printRaw(node, opts.originalText); + + // dedent the content + content = content.replace(/\r\n/g, '\n'); + const contentArr = content.split('\n').map((s) => s.trimStart()); + content = contentArr.join('\n'); + + // format + let formatttedMarkdown = textToDoc(content, { ...opts, parser: 'markdown' }); + formatttedMarkdown = stripTrailingHardline(formatttedMarkdown); + + // return formatttedMarkdown; + const attributes = path.map(print, 'attributes'); + const openingTag = group([`<${node.name}`, indent(group(attributes)), softline, '>']); + return [openingTag, indent(group([hardline, formatttedMarkdown])), hardline, ``]; + } + // TODO: ADD TYPES OR FIND ANOTHER WAY TO ACHIVE THIS // @ts-ignore if (node.__isRawHTML) { diff --git a/test/astro-prettier.test.ts b/test/astro-prettier.test.ts index 48399ed..a6f0ae9 100644 --- a/test/astro-prettier.test.ts +++ b/test/astro-prettier.test.ts @@ -147,8 +147,15 @@ test('Can format an Astro file with prettier "astroAllowShorthand: true" option' // astro option: astroAllowShorthand test('Can format an Astro file with prettier "astroAllowShorthand: false" option', Prettier, 'option-astro-allow-shorthand-false'); -test('Format nested style tag content', Prettier, 'format-nested-style-tag-content'); - -test('Format nested sass style tag content', Prettier, 'format-nested-sass-style-tag-content'); test('Format spread operator', Prettier, 'spread-operator'); + +test('Can format nested style tag content', Prettier, 'format-nested-style-tag-content'); + +test('Can format nested sass style tag content', Prettier, 'format-nested-sass-style-tag-content'); + +test('Can format the content of a markdown component as markdown', Prettier, 'markdown-component-content'); + +test.todo("Don't escape '*' inside markdown"); + +test.todo('Format jsx inside markdown'); diff --git a/test/fixtures/markdown-component-content/input.astro b/test/fixtures/markdown-component-content/input.astro new file mode 100644 index 0000000..031d0f9 --- /dev/null +++ b/test/fixtures/markdown-component-content/input.astro @@ -0,0 +1,47 @@ +--- +const text = "lorem"; +import MarkdownComponent from "astro/components/Markdown.astro" +--- +
+ +# Header + +## lorem lorem + lorem +test +
lorem
+ lorem + +{text + "lorem"}_Look,_ code blocks are formatted *too!* + +``` js +function identity(x) { return x } +``` + +```css + .bd-examples {margin-right: -.75rem;margin-left: -.75rem + } + + .bd-examples>[class^="col-"] { + padding-right: .75rem; + } +``` + +Pilot|Airport|Hours +--|:--:|--: +John Doe|SKG|1338 +Jane Roe|JFK|314 + +- - - - - - - - - - - - - - - + ++ List + + with a [link] (/to/somewhere) ++ and [another one] + + + [another one]: http://example.com 'Example title' + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Curabitur consectetur maximus risus, sed maximus tellus tincidunt et. +
+
\ No newline at end of file diff --git a/test/fixtures/markdown-component-content/output.astro b/test/fixtures/markdown-component-content/output.astro new file mode 100644 index 0000000..15e6a66 --- /dev/null +++ b/test/fixtures/markdown-component-content/output.astro @@ -0,0 +1,53 @@ +--- +const text = "lorem"; +import MarkdownComponent from "astro/components/Markdown.astro"; +--- + +
+ + # Header + + ## lorem lorem + + lorem + test + +
lorem
+ lorem + + {text + "lorem"}_Look,_ code blocks are formatted _too!_ + + ```js + function identity(x) { + return x; + } + ``` + + ```css + .bd-examples { + margin-right: -0.75rem; + margin-left: -0.75rem; + } + + .bd-examples > [class^="col-"] { + padding-right: 0.75rem; + } + ``` + + | Pilot | Airport | Hours | + | -------- | :-----: | ----: | + | John Doe | SKG | 1338 | + | Jane Roe | JFK | 314 | + + --- + + - List + - with a [link] (/to/somewhere) + - and [another one] + + [another one]: http://example.com "Example title" + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Curabitur consectetur maximus risus, sed maximus tellus tincidunt et. +
+