Fix MDX html parsing errors (#6949)
* Fix MDX html parsing errors For MDX wrap JSX parsing in a fragment before sending it of to babel for parsing and formatting. In MDX adjacent JSX tags are permitted in the root. An option sent to the babel parser omits printing the root fragment and instead just prints its children. Solves #6943 * an attempt to address JounQin's comments * use <$>...</$> instead of <>...</> * refactoring
This commit is contained in:
1 parent
61a2482c48
commit
e76ce2725b
9 files changed
+221
-90
No files matched your search
@@ -0,0 +1,43 @@
|
|||||||
|
#### Fix MDX html parsing errors ([#6949](https://github.com/prettier/prettier/pull/6949) by [@Tigge](https://github.com/Tigge) & [@thorn0](https://github.com/thorn0))
|
||||||
|
|
||||||
|
MDX parsing for JSX failed when encountering JSX elements that where not
|
||||||
|
parsable as HTML. Such as `<Tag value={{a: 'b'}}>test</Tag>`
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
````md
|
||||||
|
<!-- Input -->
|
||||||
|
|
||||||
|
<Tag value={ {a : 'b' } }>test</ Tag>
|
||||||
|
|
||||||
|
<Foo bg={ 'red' } >
|
||||||
|
<div style={{ display: 'block'} }>
|
||||||
|
<Bar >hi </Bar>
|
||||||
|
{ hello }
|
||||||
|
{ /* another comment */}
|
||||||
|
</div>
|
||||||
|
</Foo>
|
||||||
|
|
||||||
|
<!-- Prettier stable -->
|
||||||
|
|
||||||
|
```
|
||||||
|
SyntaxError: Unexpected closing tag "Tag". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags (1:35)
|
||||||
|
> 1 | <Tag value={ {a : 'b' } }>test</ Tag>
|
||||||
|
| ^
|
||||||
|
2 |
|
||||||
|
3 | <Foo bg={ 'red' } >
|
||||||
|
4 | <div style={{ display: 'block'} }>
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- Prettier master -->
|
||||||
|
|
||||||
|
<Tag value={{ a: "b" }}>test</Tag>
|
||||||
|
|
||||||
|
<Foo bg={"red"}>
|
||||||
|
<div style={{ display: "block" }}>
|
||||||
|
<Bar>hi </Bar>
|
||||||
|
{hello}
|
||||||
|
{/* another comment */}
|
||||||
|
</div>
|
||||||
|
</Foo>
|
||||||
|
|
||||||
|
````
|
||||||
@@ -10,7 +10,8 @@ function preprocess(ast, options) {
|
|||||||
return Object.assign({}, ast, {
|
return Object.assign({}, ast, {
|
||||||
type: options.parser.startsWith("__") ? "JsExpressionRoot" : "JsonRoot",
|
type: options.parser.startsWith("__") ? "JsExpressionRoot" : "JsonRoot",
|
||||||
node: ast,
|
node: ast,
|
||||||
comments: []
|
comments: [],
|
||||||
|
rootMarker: options.rootMarker
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return ast;
|
return ast;
|
||||||
|
|||||||
@@ -5398,8 +5398,12 @@ function printJSXElement(path, options, print) {
|
|||||||
containsMultipleAttributes ||
|
containsMultipleAttributes ||
|
||||||
containsMultipleExpressions;
|
containsMultipleExpressions;
|
||||||
|
|
||||||
|
const isMdxBlock = path.getParentNode().rootMarker === "mdx";
|
||||||
|
|
||||||
const rawJsxWhitespace = options.singleQuote ? "{' '}" : '{" "}';
|
const rawJsxWhitespace = options.singleQuote ? "{' '}" : '{" "}';
|
||||||
const jsxWhitespace = ifBreak(concat([rawJsxWhitespace, softline]), " ");
|
const jsxWhitespace = isMdxBlock
|
||||||
|
? concat([" "])
|
||||||
|
: ifBreak(concat([rawJsxWhitespace, softline]), " ");
|
||||||
|
|
||||||
const isFacebookTranslationTag =
|
const isFacebookTranslationTag =
|
||||||
n.openingElement &&
|
n.openingElement &&
|
||||||
@@ -5519,6 +5523,10 @@ function printJSXElement(path, options, print) {
|
|||||||
? fill(multilineChildren)
|
? fill(multilineChildren)
|
||||||
: group(concat(multilineChildren), { shouldBreak: true });
|
: group(concat(multilineChildren), { shouldBreak: true });
|
||||||
|
|
||||||
|
if (isMdxBlock) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
const multiLineElem = group(
|
const multiLineElem = group(
|
||||||
concat([
|
concat([
|
||||||
openingLines,
|
openingLines,
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ function embed(path, print, textToDoc, options) {
|
|||||||
case "importExport":
|
case "importExport":
|
||||||
return textToDoc(node.value, { parser: "babel" });
|
return textToDoc(node.value, { parser: "babel" });
|
||||||
case "jsx":
|
case "jsx":
|
||||||
return textToDoc(node.value, { parser: "__js_expression" });
|
return textToDoc(`<$>${node.value}</$>`, {
|
||||||
|
parser: "__js_expression",
|
||||||
|
rootMarker: "mdx"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ const parseFrontMatter = require("../utils/front-matter");
|
|||||||
const { mapAst, INLINE_NODE_WRAPPER_TYPES } = require("./utils");
|
const { mapAst, INLINE_NODE_WRAPPER_TYPES } = require("./utils");
|
||||||
const mdx = require("./mdx");
|
const mdx = require("./mdx");
|
||||||
const remarkMath = require("remark-math");
|
const remarkMath = require("remark-math");
|
||||||
const htmlParser = require("../language-html/parser-html").parsers.html;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* based on [MDAST](https://github.com/syntax-tree/mdast) with following modifications:
|
* based on [MDAST](https://github.com/syntax-tree/mdast) with following modifications:
|
||||||
@@ -60,29 +59,7 @@ function htmlToJsx() {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodes = htmlParser.parse(node.value).children;
|
return Object.assign({}, node, { type: "jsx" });
|
||||||
|
|
||||||
// find out if there are adjacent JSX elements which should be allowed in mdx alike in markdown
|
|
||||||
if (nodes.length <= 1) {
|
|
||||||
return Object.assign({}, node, { type: "jsx" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes.reduce((newNodes, { sourceSpan: position, type }) => {
|
|
||||||
const value = node.value.slice(
|
|
||||||
position.start.offset,
|
|
||||||
position.end.offset
|
|
||||||
);
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
newNodes.push({
|
|
||||||
type: type === "element" ? "jsx" : type,
|
|
||||||
value,
|
|
||||||
position
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return newNodes;
|
|
||||||
}, []);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,7 @@ const { replaceEndOfLineWith } = require("../common/util");
|
|||||||
|
|
||||||
const TRAILING_HARDLINE_NODES = ["importExport"];
|
const TRAILING_HARDLINE_NODES = ["importExport"];
|
||||||
const SINGLE_LINE_NODE_TYPES = ["heading", "tableCell", "link"];
|
const SINGLE_LINE_NODE_TYPES = ["heading", "tableCell", "link"];
|
||||||
const SIBLING_NODE_TYPES = [
|
const SIBLING_NODE_TYPES = ["listItem", "definition", "footnoteDefinition"];
|
||||||
"listItem",
|
|
||||||
"definition",
|
|
||||||
"footnoteDefinition",
|
|
||||||
"jsx"
|
|
||||||
];
|
|
||||||
|
|
||||||
function genericPrint(path, options, print) {
|
function genericPrint(path, options, print) {
|
||||||
const node = path.getValue();
|
const node = path.getValue();
|
||||||
@@ -769,55 +764,35 @@ function isPrettierIgnore(node) {
|
|||||||
return match === null ? false : match[1] ? match[1] : "next";
|
return match === null ? false : match[1] ? match[1] : "next";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInlineNode(node) {
|
function shouldNotPrePrintHardline(node, data) {
|
||||||
return node && INLINE_NODE_TYPES.indexOf(node.type) !== -1;
|
const isFirstNode = data.parts.length === 0;
|
||||||
}
|
const isInlineNode = INLINE_NODE_TYPES.indexOf(node.type) !== -1;
|
||||||
|
|
||||||
function isEndsWithHardLine(node) {
|
|
||||||
return node && /\n+$/.test(node.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function last(nodes) {
|
|
||||||
return nodes && nodes[nodes.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldNotPrePrintHardline(node, { parentNode, parts, prevNode }) {
|
|
||||||
const isFirstNode = parts.length === 0;
|
|
||||||
|
|
||||||
const isInlineHTML =
|
const isInlineHTML =
|
||||||
node.type === "html" &&
|
node.type === "html" &&
|
||||||
INLINE_NODE_WRAPPER_TYPES.indexOf(parentNode.type) !== -1;
|
INLINE_NODE_WRAPPER_TYPES.indexOf(data.parentNode.type) !== -1;
|
||||||
|
|
||||||
const isAfterHardlineNode =
|
return isFirstNode || isInlineNode || isInlineHTML;
|
||||||
prevNode &&
|
|
||||||
(isEndsWithHardLine(prevNode) ||
|
|
||||||
isEndsWithHardLine(last(prevNode.children)));
|
|
||||||
|
|
||||||
return (
|
|
||||||
isFirstNode || isInlineNode(node) || isInlineHTML || isAfterHardlineNode
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldPrePrintDoubleHardline(node, { parentNode, prevNode }) {
|
function shouldPrePrintDoubleHardline(node, data) {
|
||||||
const prevNodeType = prevNode && prevNode.type;
|
const isSequence = (data.prevNode && data.prevNode.type) === node.type;
|
||||||
const nodeType = node.type;
|
|
||||||
|
|
||||||
const isSequence = prevNodeType === nodeType;
|
|
||||||
const isSiblingNode =
|
const isSiblingNode =
|
||||||
isSequence && SIBLING_NODE_TYPES.indexOf(nodeType) !== -1;
|
isSequence && SIBLING_NODE_TYPES.indexOf(node.type) !== -1;
|
||||||
|
|
||||||
const isInTightListItem = parentNode.type === "listItem" && !parentNode.loose;
|
const isInTightListItem =
|
||||||
const isPrevNodeLooseListItem = prevNodeType === "listItem" && prevNode.loose;
|
data.parentNode.type === "listItem" && !data.parentNode.loose;
|
||||||
const isPrevNodePrettierIgnore = isPrettierIgnore(prevNode) === "next";
|
|
||||||
|
const isPrevNodeLooseListItem =
|
||||||
|
data.prevNode && data.prevNode.type === "listItem" && data.prevNode.loose;
|
||||||
|
|
||||||
|
const isPrevNodePrettierIgnore = isPrettierIgnore(data.prevNode) === "next";
|
||||||
|
|
||||||
const isBlockHtmlWithoutBlankLineBetweenPrevHtml =
|
const isBlockHtmlWithoutBlankLineBetweenPrevHtml =
|
||||||
nodeType === "html" &&
|
node.type === "html" &&
|
||||||
prevNodeType === "html" &&
|
data.prevNode &&
|
||||||
prevNode.position.end.line + 1 === node.position.start.line;
|
data.prevNode.type === "html" &&
|
||||||
|
data.prevNode.position.end.line + 1 === node.position.start.line;
|
||||||
const isJsxInlineSibling =
|
|
||||||
(prevNodeType === "jsx" && isInlineNode(node)) ||
|
|
||||||
(nodeType === "jsx" && isInlineNode(prevNode));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isPrevNodeLooseListItem ||
|
isPrevNodeLooseListItem ||
|
||||||
@@ -825,8 +800,7 @@ function shouldPrePrintDoubleHardline(node, { parentNode, prevNode }) {
|
|||||||
isSiblingNode ||
|
isSiblingNode ||
|
||||||
isInTightListItem ||
|
isInTightListItem ||
|
||||||
isPrevNodePrettierIgnore ||
|
isPrevNodePrettierIgnore ||
|
||||||
isBlockHtmlWithoutBlankLineBetweenPrevHtml ||
|
isBlockHtmlWithoutBlankLineBetweenPrevHtml
|
||||||
isJsxInlineSibling
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,21 +230,11 @@ function mapAst(ast, handler) {
|
|||||||
return (function preorder(node, index, parentStack) {
|
return (function preorder(node, index, parentStack) {
|
||||||
parentStack = parentStack || [];
|
parentStack = parentStack || [];
|
||||||
|
|
||||||
let newNode = handler(node, index, parentStack);
|
const newNode = Object.assign({}, handler(node, index, parentStack));
|
||||||
if (Array.isArray(newNode)) {
|
|
||||||
return newNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
newNode = Object.assign({}, newNode);
|
|
||||||
if (newNode.children) {
|
if (newNode.children) {
|
||||||
newNode.children = newNode.children.reduce((nodes, child, index) => {
|
newNode.children = newNode.children.map((child, index) => {
|
||||||
let newNodes = preorder(child, index, [newNode].concat(parentStack));
|
return preorder(child, index, [newNode].concat(parentStack));
|
||||||
if (!Array.isArray(newNodes)) {
|
});
|
||||||
newNodes = [newNodes];
|
|
||||||
}
|
|
||||||
nodes.push.apply(nodes, newNodes);
|
|
||||||
return nodes;
|
|
||||||
}, []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
|
|||||||
@@ -276,7 +276,8 @@ printWidth: 80
|
|||||||
|
|
||||||
<Hello>
|
<Hello>
|
||||||
test <World /> test
|
test <World /> test
|
||||||
</Hello>123
|
</Hello>
|
||||||
|
123
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -352,7 +353,8 @@ semi: false
|
|||||||
|
|
||||||
<Hello>
|
<Hello>
|
||||||
test <World /> test
|
test <World /> test
|
||||||
</Hello>123
|
</Hello>
|
||||||
|
123
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -438,6 +440,117 @@ const a = 1
|
|||||||
================================================================================
|
================================================================================
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`levels.mdx 1`] = `
|
||||||
|
====================================options=====================================
|
||||||
|
parsers: ["mdx"]
|
||||||
|
printWidth: 80
|
||||||
|
| printWidth
|
||||||
|
=====================================input======================================
|
||||||
|
import { Foo, Bar } from './Fixture'
|
||||||
|
|
||||||
|
# Hello, world!
|
||||||
|
|
||||||
|
<Foo bg='red'>
|
||||||
|
<div style={{ display: 'block'} }>
|
||||||
|
<Bar >hi </Bar>
|
||||||
|
{ hello }
|
||||||
|
{ /* another comment */}
|
||||||
|
</div>
|
||||||
|
</Foo>
|
||||||
|
|
||||||
|
asdfsdf <strong style={{fontWeight: 'bolder'}}>asdfasdf</strong>
|
||||||
|
|
||||||
|
<Foo/>
|
||||||
|
test
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li >item {' '} </li>
|
||||||
|
<li/>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
|
||||||
|
=====================================output=====================================
|
||||||
|
import { Foo, Bar } from "./Fixture";
|
||||||
|
|
||||||
|
# Hello, world!
|
||||||
|
|
||||||
|
<Foo bg="red">
|
||||||
|
<div style={{ display: "block" }}>
|
||||||
|
<Bar>hi </Bar>
|
||||||
|
{hello}
|
||||||
|
{/* another comment */}
|
||||||
|
</div>
|
||||||
|
</Foo>
|
||||||
|
|
||||||
|
asdfsdf <strong style={{fontWeight: 'bolder'}}>asdfasdf</strong>
|
||||||
|
|
||||||
|
<Foo />
|
||||||
|
test
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>item </li>
|
||||||
|
<li />
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`levels.mdx 2`] = `
|
||||||
|
====================================options=====================================
|
||||||
|
parsers: ["mdx"]
|
||||||
|
printWidth: 80
|
||||||
|
semi: false
|
||||||
|
| printWidth
|
||||||
|
=====================================input======================================
|
||||||
|
import { Foo, Bar } from './Fixture'
|
||||||
|
|
||||||
|
# Hello, world!
|
||||||
|
|
||||||
|
<Foo bg='red'>
|
||||||
|
<div style={{ display: 'block'} }>
|
||||||
|
<Bar >hi </Bar>
|
||||||
|
{ hello }
|
||||||
|
{ /* another comment */}
|
||||||
|
</div>
|
||||||
|
</Foo>
|
||||||
|
|
||||||
|
asdfsdf <strong style={{fontWeight: 'bolder'}}>asdfasdf</strong>
|
||||||
|
|
||||||
|
<Foo/>
|
||||||
|
test
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li >item {' '} </li>
|
||||||
|
<li/>
|
||||||
|
</ul >
|
||||||
|
|
||||||
|
|
||||||
|
=====================================output=====================================
|
||||||
|
import { Foo, Bar } from "./Fixture"
|
||||||
|
|
||||||
|
# Hello, world!
|
||||||
|
|
||||||
|
<Foo bg="red">
|
||||||
|
<div style={{ display: "block" }}>
|
||||||
|
<Bar>hi </Bar>
|
||||||
|
{hello}
|
||||||
|
{/* another comment */}
|
||||||
|
</div>
|
||||||
|
</Foo>
|
||||||
|
|
||||||
|
asdfsdf <strong style={{fontWeight: 'bolder'}}>asdfasdf</strong>
|
||||||
|
|
||||||
|
<Foo />
|
||||||
|
test
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>item </li>
|
||||||
|
<li />
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`mixed.mdx 1`] = `
|
exports[`mixed.mdx 1`] = `
|
||||||
====================================options=====================================
|
====================================options=====================================
|
||||||
parsers: ["mdx"]
|
parsers: ["mdx"]
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Foo, Bar } from './Fixture'
|
||||||
|
|
||||||
|
# Hello, world!
|
||||||
|
|
||||||
|
<Foo bg='red'>
|
||||||
|
<div style={{ display: 'block'} }>
|
||||||
|
<Bar >hi </Bar>
|
||||||
|
{ hello }
|
||||||
|
{ /* another comment */}
|
||||||
|
</div>
|
||||||
|
</Foo>
|
||||||
|
|
||||||
|
asdfsdf <strong style={{fontWeight: 'bolder'}}>asdfasdf</strong>
|
||||||
|
|
||||||
|
<Foo/>
|
||||||
|
test
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li >item {' '} </li>
|
||||||
|
<li/>
|
||||||
|
</ul >
|
||||||
|
|
||||||
Reference in new issue
Block a user