Handlebars: fix formatting of attributes (#10145)
* Handlebars: fix formatting of attributes * Add group only for class * Add changelog * Update clean.js * Simplify ConcatStatement * Lowercase attr name in clean * Use text instead of n.chars
This commit is contained in:
1 parent
12e82a89af
commit
8846448b85
12 files changed
+259
-194
No files matched your search
@@ -9,6 +9,11 @@ function clean(ast, newNode /*, parent*/) {
|
||||
}
|
||||
newNode.chars = trimmed;
|
||||
}
|
||||
|
||||
// `class` is reformatted
|
||||
if (ast.type === "AttrNode" && ast.name.toLowerCase() === "class") {
|
||||
delete newNode.value;
|
||||
}
|
||||
}
|
||||
clean.ignoredProperties = new Set(["loc", "selfClosing"]);
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
const {
|
||||
builders: { dedent, group, hardline, ifBreak, indent, join, line, softline },
|
||||
utils: { getDocParts },
|
||||
} = require("../document");
|
||||
const { isNonEmptyArray } = require("../common/util");
|
||||
const { isNonEmptyArray, replaceEndOfLineWith } = require("../common/util");
|
||||
|
||||
const { literalline } = require("../document/doc-builders");
|
||||
const { locStart, locEnd } = require("./loc");
|
||||
const clean = require("./clean");
|
||||
const {
|
||||
@@ -152,23 +152,34 @@ function print(path, options, print) {
|
||||
if (isEmptyText && locStart(n.value) === locEnd(n.value)) {
|
||||
return n.name;
|
||||
}
|
||||
const value = path.call(print, "value");
|
||||
const quotedValue = isText
|
||||
? printStringLiteral(
|
||||
typeof value === "string" ? value : getDocParts(value).join(),
|
||||
options
|
||||
)
|
||||
: value;
|
||||
return [n.name, "=", quotedValue];
|
||||
|
||||
// Let's assume quotes inside the content of text nodes are already
|
||||
// properly escaped with entities, otherwise the parse wouldn't have parsed them.
|
||||
const quote = isText
|
||||
? chooseEnclosingQuote(options, n.value.chars).quote
|
||||
: n.value.type === "ConcatStatement"
|
||||
? chooseEnclosingQuote(
|
||||
options,
|
||||
n.value.parts
|
||||
.filter((part) => part.type === "TextNode")
|
||||
.map((part) => part.chars)
|
||||
.join("")
|
||||
).quote
|
||||
: "";
|
||||
|
||||
const valueDoc = path.call(print, "value");
|
||||
|
||||
return [
|
||||
n.name,
|
||||
"=",
|
||||
quote,
|
||||
n.name === "class" && quote ? group(indent(valueDoc)) : valueDoc,
|
||||
quote,
|
||||
];
|
||||
}
|
||||
|
||||
case "ConcatStatement": {
|
||||
const quote = options.singleQuote ? "'" : '"';
|
||||
return [
|
||||
quote,
|
||||
...path.map((partPath) => print(partPath), "parts"),
|
||||
quote,
|
||||
];
|
||||
return path.map(print, "parts");
|
||||
}
|
||||
|
||||
case "Hash": {
|
||||
@@ -178,18 +189,57 @@ function print(path, options, print) {
|
||||
return [n.key, "=", path.call(print, "value")];
|
||||
}
|
||||
case "TextNode": {
|
||||
const inAttrNode = path.stack.includes("attributes");
|
||||
/* if `{{my-component}}` (or any text containing "{{")
|
||||
* makes it to the TextNode, it means it was escaped,
|
||||
* so let's print it escaped, ie.; `\{{my-component}}` */
|
||||
let text = n.chars.replace(/{{/g, "\\{{");
|
||||
|
||||
if (options.htmlWhitespaceSensitivity === "strict" && !inAttrNode) {
|
||||
const attrName = getCurrentAttributeName(path);
|
||||
|
||||
if (attrName) {
|
||||
// TODO: format style and srcset attributes
|
||||
if (attrName === "class") {
|
||||
const formattedClasses = text.trim().split(/\s+/).join(" ");
|
||||
|
||||
let leadingSpace = false;
|
||||
let trailingSpace = false;
|
||||
|
||||
if (isParentOfSomeType(path, ["ConcatStatement"])) {
|
||||
if (
|
||||
isPreviousNodeOfSomeType(path, ["MustacheStatement"]) &&
|
||||
/^\s/.test(text)
|
||||
) {
|
||||
leadingSpace = true;
|
||||
}
|
||||
if (
|
||||
isNextNodeOfSomeType(path, ["MustacheStatement"]) &&
|
||||
/\s$/.test(text) &&
|
||||
formattedClasses !== ""
|
||||
) {
|
||||
trailingSpace = true;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
leadingSpace ? line : "",
|
||||
formattedClasses,
|
||||
trailingSpace ? line : "",
|
||||
];
|
||||
}
|
||||
|
||||
return replaceEndOfLineWith(text, literalline);
|
||||
}
|
||||
|
||||
if (options.htmlWhitespaceSensitivity === "strict") {
|
||||
// https://infra.spec.whatwg.org/#ascii-whitespace
|
||||
const leadingWhitespacesRE = /^[\t\n\f\r ]*/;
|
||||
const trailingWhitespacesRE = /[\t\n\f\r ]*$/;
|
||||
const whitespacesOnlyRE = /^[\t\n\f\r ]*$/;
|
||||
|
||||
if (whitespacesOnlyRE.test(n.chars)) {
|
||||
if (whitespacesOnlyRE.test(text)) {
|
||||
let breaks = [line];
|
||||
|
||||
const newlines = countNewLines(n.chars);
|
||||
const newlines = countNewLines(text);
|
||||
if (newlines) {
|
||||
breaks = generateHardlines(newlines, 2);
|
||||
}
|
||||
@@ -201,10 +251,8 @@ function print(path, options, print) {
|
||||
return breaks;
|
||||
}
|
||||
|
||||
const [lead] = n.chars.match(leadingWhitespacesRE);
|
||||
const [tail] = n.chars.match(trailingWhitespacesRE);
|
||||
|
||||
let text = n.chars;
|
||||
const [lead] = text.match(leadingWhitespacesRE);
|
||||
const [tail] = text.match(trailingWhitespacesRE);
|
||||
|
||||
let leadBreaks = [];
|
||||
if (lead) {
|
||||
@@ -240,11 +288,11 @@ function print(path, options, print) {
|
||||
const maxLineBreaksToPreserve = 2;
|
||||
const isFirstElement = !getPreviousNode(path);
|
||||
const isLastElement = !getNextNode(path);
|
||||
const isWhitespaceOnly = !/\S/.test(n.chars);
|
||||
const lineBreaksCount = countNewLines(n.chars);
|
||||
const isWhitespaceOnly = !/\S/.test(text);
|
||||
const lineBreaksCount = countNewLines(text);
|
||||
|
||||
let leadingLineBreaksCount = countLeadingNewLines(n.chars);
|
||||
let trailingLineBreaksCount = countTrailingNewLines(n.chars);
|
||||
let leadingLineBreaksCount = countLeadingNewLines(text);
|
||||
let trailingLineBreaksCount = countTrailingNewLines(text);
|
||||
|
||||
if (
|
||||
(isFirstElement || isLastElement) &&
|
||||
@@ -270,34 +318,6 @@ function print(path, options, print) {
|
||||
}
|
||||
}
|
||||
|
||||
if (inAttrNode) {
|
||||
// TODO: format style and srcset attributes
|
||||
if (!isInAttributeOfName(path, "class")) {
|
||||
return n.chars;
|
||||
}
|
||||
|
||||
let leadingSpace = "";
|
||||
let trailingSpace = "";
|
||||
|
||||
if (isParentOfSomeType(path, ["ConcatStatement"])) {
|
||||
if (isPreviousNodeOfSomeType(path, ["MustacheStatement"])) {
|
||||
leadingSpace = " ";
|
||||
}
|
||||
if (isNextNodeOfSomeType(path, ["MustacheStatement"])) {
|
||||
trailingSpace = " ";
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
...generateHardlines(leadingLineBreaksCount, maxLineBreaksToPreserve),
|
||||
n.chars.replace(/^\s+/g, leadingSpace).replace(/\s+$/, trailingSpace),
|
||||
...generateHardlines(
|
||||
trailingLineBreaksCount,
|
||||
maxLineBreaksToPreserve
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
let leadingSpace = "";
|
||||
let trailingSpace = "";
|
||||
|
||||
@@ -325,15 +345,6 @@ function print(path, options, print) {
|
||||
trailingSpace = "";
|
||||
}
|
||||
|
||||
let text = n.chars;
|
||||
/* if `{{my-component}}` (or any text starting with a mustache)
|
||||
* makes it to the TextNode,
|
||||
* it means it was escaped,
|
||||
* so let's print it escaped, ie.; `\{{my-component}}` */
|
||||
if (text.startsWith("{{") && text.includes("}}")) {
|
||||
text = "\\" + text;
|
||||
}
|
||||
|
||||
return [
|
||||
...generateHardlines(leadingLineBreaksCount, maxLineBreaksToPreserve),
|
||||
text.replace(/^\s+/g, leadingSpace).replace(/\s+$/, trailingSpace),
|
||||
@@ -617,13 +628,13 @@ function printInverse(path, print, options) {
|
||||
|
||||
/* TextNode print helpers */
|
||||
|
||||
function isInAttributeOfName(path, type) {
|
||||
return (
|
||||
(isParentOfSomeType(path, ["AttrNode"]) &&
|
||||
path.getParentNode().name.toLowerCase() === type) ||
|
||||
(isParentOfSomeType(path, ["ConcatStatement"]) &&
|
||||
path.getParentNode(1).name.toLowerCase() === type)
|
||||
);
|
||||
function getCurrentAttributeName(path) {
|
||||
for (let depth = 0; depth < 2; depth++) {
|
||||
const parentNode = path.getParentNode(depth);
|
||||
if (parentNode && parentNode.type === "AttrNode") {
|
||||
return parentNode.name.toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function countNewLines(string) {
|
||||
@@ -662,6 +673,11 @@ function generateHardlines(number = 0, max = 0) {
|
||||
* @param {object} options - the prettier options object
|
||||
*/
|
||||
function printStringLiteral(stringLiteral, options) {
|
||||
const { quote, regex } = chooseEnclosingQuote(options, stringLiteral);
|
||||
return [quote, stringLiteral.replace(regex, `\\${quote}`), quote];
|
||||
}
|
||||
|
||||
function chooseEnclosingQuote(options, stringLiteral) {
|
||||
const double = { quote: '"', regex: /"/g };
|
||||
const single = { quote: "'", regex: /'/g };
|
||||
|
||||
@@ -685,13 +701,7 @@ function printStringLiteral(stringLiteral, options) {
|
||||
shouldUseAlternateQuote = numPreferredQuotes > numAlternateQuotes;
|
||||
}
|
||||
|
||||
const enclosingQuote = shouldUseAlternateQuote ? alternate : preferred;
|
||||
const escapedStringLiteral = stringLiteral.replace(
|
||||
enclosingQuote.regex,
|
||||
`\\${enclosingQuote.quote}`
|
||||
);
|
||||
|
||||
return [enclosingQuote.quote, escapedStringLiteral, enclosingQuote.quote];
|
||||
return shouldUseAlternateQuote ? alternate : preferred;
|
||||
}
|
||||
|
||||
/* SubExpression print helpers */
|
||||
|
||||
Reference in new issue
Block a user