diff --git a/src/language-html/embed.js b/src/language-html/embed.js
index ada7ace08..74c3287ae 100644
--- a/src/language-html/embed.js
+++ b/src/language-html/embed.js
@@ -57,7 +57,7 @@ function embed(path, print, textToDoc, options) {
*/
if (/(^@)|(^v-)|:/.test(node.key) && !/^\w+$/.test(node.value)) {
const doc = textToDoc(node.value, {
- parser: parseJavaScriptExpression,
+ parser: "__js_expression",
// Use singleQuote since HTML attributes use double-quotes.
// TODO(azz): We still need to do an entity escape on the attribute.
singleQuote: true
@@ -106,16 +106,6 @@ function replaceNewlinesWithLiterallines(doc) {
);
}
-function parseJavaScriptExpression(text, parsers) {
- // Force parsing as an expression
- const ast = parsers.babylon(`(${text})`);
- // Extract expression from the declaration
- return {
- type: "File",
- program: ast.program.body[0].expression
- };
-}
-
function getText(options, node) {
return options.originalText.slice(
options.locStart(node),
diff --git a/src/language-html/printer-htmlparser2.js b/src/language-html/printer-htmlparser2.js
index 2ebec1c5e..2b6232658 100644
--- a/src/language-html/printer-htmlparser2.js
+++ b/src/language-html/printer-htmlparser2.js
@@ -2,7 +2,7 @@
const embed = require("./embed");
const clean = require("./clean");
-const { getLast, hasIgnoreComment } = require("../common/util");
+const { getLast } = require("../common/util");
const { isNextLineEmpty } = require("../common/util-shared");
const {
builders: {
@@ -17,16 +17,18 @@ const {
},
utils: { willBreak, isLineNext, isEmpty }
} = require("../doc");
+const {
+ hasPrettierIgnore,
+ isBooleanAttributeNode,
+ isPreTagNode,
+ isScriptTagNode,
+ isTextAreaTagNode,
+ isVoidTagNode,
+ isWhitespaceOnlyText
+} = require("./utils");
function genericPrint(path, options, print) {
const n = path.getValue();
- if (!n) {
- return "";
- }
-
- if (typeof n === "string") {
- return n;
- }
switch (n.type) {
case "root": {
@@ -43,10 +45,7 @@ function genericPrint(path, options, print) {
case "text": {
const parentNode = path.getParentNode();
- if (
- parentNode &&
- (isPreTagNode(parentNode) || isTextAreaTagNode(parentNode))
- ) {
+ if (isPreTagNode(parentNode) || isTextAreaTagNode(parentNode)) {
return n.data;
}
@@ -56,18 +55,17 @@ function genericPrint(path, options, print) {
case "style":
case "tag": {
const isVoid = isVoidTagNode(n);
- const openingPrinted = printOpeningPart(path, print);
+ const openingPrinted = printOpeningTag(path, print, isVoid);
// Print self closing tag
if (isVoid) {
- return concat([openingPrinted]);
+ return openingPrinted;
}
- const closingPrinted = printClosingPart(path, print);
- const hasChildren = n.children.length > 0;
+ const closingPrinted = printClosingTag(n);
// Print tags without children
- if (!hasChildren) {
+ if (n.children.length === 0) {
return concat([openingPrinted, closingPrinted]);
}
@@ -102,14 +100,12 @@ function genericPrint(path, options, print) {
);
}
- const containsTag =
- n.children.filter(
- node => ["script", "style", "tag"].indexOf(node.type) !== -1
- ).length > 0;
- const containsMultipleAttributes = n.attributes.length > 1;
+ const containsTag = n.children.some(
+ child => ["script", "style", "tag"].indexOf(child.type) !== -1
+ );
let forcedBreak =
- willBreak(openingPrinted) || containsTag || containsMultipleAttributes;
+ willBreak(openingPrinted) || containsTag || n.attributes.length > 1;
// Trim trailing lines (or empty strings)
while (
@@ -122,31 +118,21 @@ function genericPrint(path, options, print) {
// Trim leading lines (or empty strings)
while (
children.length &&
- (isLineNext(children[0]) || isEmpty(children[0])) &&
- (isLineNext(children[1]) || isEmpty(children[1]))
+ (isLineNext(children[0]) || isEmpty(children[0]))
) {
children.shift();
- children.shift();
}
// Detect whether we will force this element to output over multiple lines.
- const multilineChildren = [];
+ if (children.some(doc => willBreak(doc))) {
+ forcedBreak = true;
+ }
- children.forEach(child => {
- multilineChildren.push(child);
-
- if (willBreak(child)) {
- forcedBreak = true;
- }
- });
-
- const containsOnlyEmptyTextNodes = n.children.every(node => {
- return node.type === "text" && /^\s+$/.test(node.data);
- });
+ const containsOnlyEmptyTextNodes = n.children.every(isWhitespaceOnlyText);
const printedMultilineChildren = concat([
!isScriptTag && !containsOnlyEmptyTextNodes ? hardline : "",
- group(concat(multilineChildren), { shouldBreak: true })
+ group(concat(children), { shouldBreak: true })
]);
const multiLineElem = group(
@@ -176,12 +162,6 @@ function genericPrint(path, options, print) {
return n.key;
}
- const parentNode = path.getParentNode();
-
- if (!parentNode || !parentNode.sourceCodeLocation) {
- return n.key;
- }
-
const originalAttributeSourceCode = options.originalText.slice(
n.sourceCodeLocation.startOffset,
n.sourceCodeLocation.endOffset
@@ -203,81 +183,8 @@ function genericPrint(path, options, print) {
}
}
-// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
-function isBooleanAttributeNode(node) {
- return (
- node.type === "attribute" &&
- [
- "allowfullscreen",
- "allowpaymentrequest",
- "async",
- "autofocus",
- "autoplay",
- "checked",
- "controls",
- "default",
- "defer",
- "disabled",
- "formnovalidate",
- "hidden",
- "ismap",
- "itemscope",
- "loop",
- "multiple",
- "muted",
- "nomodule",
- "novalidate",
- "open",
- "readonly",
- "required",
- "reversed",
- "selected",
- "typemustmatch"
- ].indexOf(node.key) !== -1
- );
-}
-
-// http://w3c.github.io/html/single-page.html#void-elements
-function isVoidTagNode(node) {
- return (
- node.type === "tag" &&
- [
- "area",
- "base",
- "br",
- "col",
- "embed",
- "hr",
- "img",
- "input",
- "link",
- "meta",
- "param",
- "source",
- "track",
- "wbr"
- ].indexOf(node.name) !== -1
- );
-}
-
-function isPreTagNode(node) {
- return node.type === "tag" && node.name === "pre";
-}
-
-function isTextAreaTagNode(node) {
- return node.type === "tag" && node.name === "textarea";
-}
-
-function isScriptTagNode(node) {
- return (
- (node.type === "script" || node.type === "style") &&
- ["script", "style"].indexOf(node.name) !== -1
- );
-}
-
-function printOpeningPart(path, print) {
+function printOpeningTag(path, print, isVoid) {
const n = path.getValue();
- const isVoid = isVoidTagNode(n);
// Don't break self-closing elements with no attributes
if (isVoid && !n.attributes.length) {
@@ -287,13 +194,7 @@ function printOpeningPart(path, print) {
// Don't break up opening elements with a single long text attribute
if (n.attributes && n.attributes.length === 1 && n.attributes[0].value) {
return group(
- concat([
- "<",
- path.call(print, "name"),
- " ",
- concat(path.map(print, "attributes")),
- ">"
- ])
+ concat(["<", n.name, " ", concat(path.map(print, "attributes")), ">"])
);
}
@@ -312,33 +213,17 @@ function printOpeningPart(path, print) {
);
}
-function printClosingPart(path, print) {
- return concat(["", path.call(print, "name"), ">"]);
+function printClosingTag(node) {
+ return concat(["", node.name, ">"]);
}
function printChildren(path, print, options) {
const parts = [];
- path.map((childPath, i) => {
- const parentNode = childPath.getParentNode();
+ path.map(childPath => {
const child = childPath.getValue();
- const printedChild = print(childPath);
- if (
- parentNode &&
- parentNode.children[i - 2] &&
- parentNode.children[i - 2].type === "comment" &&
- parentNode.children[i - 2].data.trim() === "prettier-ignore"
- ) {
- parts.push(
- options.originalText.slice(
- options.locStart(child),
- options.locEnd(child)
- )
- );
- } else {
- parts.push(printedChild);
- }
+ parts.push(print(childPath));
if (child.type !== "text" && child.type !== "directive") {
parts.push(hardline);
@@ -356,5 +241,5 @@ module.exports = {
print: genericPrint,
massageAstNode: clean,
embed,
- hasPrettierIgnore: hasIgnoreComment
+ hasPrettierIgnore
};
diff --git a/src/language-html/utils.js b/src/language-html/utils.js
new file mode 100644
index 000000000..ec0bbc99b
--- /dev/null
+++ b/src/language-html/utils.js
@@ -0,0 +1,121 @@
+"use strict";
+
+// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
+const BOOLEAN_ATTRIBUTES = [
+ "allowfullscreen",
+ "allowpaymentrequest",
+ "async",
+ "autofocus",
+ "autoplay",
+ "checked",
+ "controls",
+ "default",
+ "defer",
+ "disabled",
+ "formnovalidate",
+ "hidden",
+ "ismap",
+ "itemscope",
+ "loop",
+ "multiple",
+ "muted",
+ "nomodule",
+ "novalidate",
+ "open",
+ "readonly",
+ "required",
+ "reversed",
+ "selected",
+ "typemustmatch"
+];
+
+// http://w3c.github.io/html/single-page.html#void-elements
+const VOID_TAGS = [
+ "area",
+ "base",
+ "br",
+ "col",
+ "embed",
+ "hr",
+ "img",
+ "input",
+ "link",
+ "meta",
+ "param",
+ "source",
+ "track",
+ "wbr"
+];
+
+function hasPrettierIgnore(path) {
+ const node = path.getValue();
+
+ if (isWhitespaceOnlyText(node) || node.type === "attribute") {
+ return false;
+ }
+
+ const parentNode = path.getParentNode();
+
+ if (!parentNode) {
+ return false;
+ }
+
+ const index = path.getName();
+
+ if (typeof index !== "number" || index === 0) {
+ return false;
+ }
+
+ const prevNode = parentNode.children[index - 1];
+
+ if (isPrettierIgnore(prevNode)) {
+ return true;
+ }
+
+ if (!isWhitespaceOnlyText(prevNode)) {
+ return false;
+ }
+
+ const prevPrevNode = parentNode.children[index - 2];
+ return prevPrevNode && isPrettierIgnore(prevPrevNode);
+}
+
+function isPrettierIgnore(node) {
+ return node.type === "comment" && node.data.trim() === "prettier-ignore";
+}
+
+function isWhitespaceOnlyText(node) {
+ return node.type === "text" && node.data.trim().length === 0;
+}
+
+function isBooleanAttributeNode(node) {
+ return (
+ node.type === "attribute" && BOOLEAN_ATTRIBUTES.indexOf(node.key) !== -1
+ );
+}
+
+function isVoidTagNode(node) {
+ return node.type === "tag" && VOID_TAGS.indexOf(node.name) !== -1;
+}
+
+function isPreTagNode(node) {
+ return node.type === "tag" && node.name === "pre";
+}
+
+function isTextAreaTagNode(node) {
+ return node.type === "tag" && node.name === "textarea";
+}
+
+function isScriptTagNode(node) {
+ return node.type === "script" || node.type === "style";
+}
+
+module.exports = {
+ hasPrettierIgnore,
+ isBooleanAttributeNode,
+ isWhitespaceOnlyText,
+ isPreTagNode,
+ isScriptTagNode,
+ isTextAreaTagNode,
+ isVoidTagNode
+};