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([""]); +function printClosingTag(node) { + return concat([""]); } 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 +};