Merge branch 'main' into next

This commit is contained in:
fisker Cheung authored and GitHub committed 2021-09-09 10:08:38 +08:00
commit e0f1f01ab4
7 files changed
+854 -772

No files matched your search

+2 -2
View File
@@ -19,13 +19,13 @@ jobs:
- "macos-latest"
- "windows-latest"
node:
- "16"
- "16.8.0"
- "14"
- "12"
include:
# only enable coverage on the fastest job
- os: "ubuntu-latest"
node: "16"
node: "16.8.0"
ENABLE_CODE_COVERAGE: true
FULL_TEST: true
CHECK_TEST_PARSERS: true
+2 -2
View File
@@ -89,13 +89,13 @@ jobs:
- "macos-latest"
- "windows-latest"
node:
- "16"
- "16.8.0"
- "14"
- "12"
- "10"
include:
- os: "ubuntu-latest"
node: "16"
node: "16.8.0"
FULL_TEST: true
exclude:
- os: "macos-latest"
+402
View File
@@ -0,0 +1,402 @@
"use strict";
const {
builders: { breakParent, group, hardline, indent, line, fill, softline },
utils: { mapDoc, replaceTextEndOfLine },
} = require("../document/index.js");
const printFrontMatter = require("../utils/front-matter/print.js");
const {
printClosingTag,
printClosingTagSuffix,
needsToBorrowPrevClosingTagEndMarker,
printOpeningTagPrefix,
printOpeningTag,
} = require("./print/tag.js");
const { printImgSrcset, printClassNames } = require("./syntax-attribute.js");
const {
printVueFor,
printVueBindings,
isVueEventBindingExpression,
} = require("./syntax-vue.js");
const {
isScriptLikeTag,
isVueNonHtmlBlock,
inferScriptParser,
htmlTrimPreserveIndentation,
dedentString,
unescapeQuoteEntities,
isVueSlotAttribute,
isVueSfcBindingsAttribute,
getTextValueParts,
} = require("./utils.js");
const getNodeContent = require("./get-node-content.js");
function printEmbeddedAttributeValue(node, originalTextToDoc, options) {
const isKeyMatched = (patterns) =>
new RegExp(patterns.join("|")).test(node.fullName);
const getValue = () => unescapeQuoteEntities(node.value);
let shouldHug = false;
const __onHtmlBindingRoot = (root, options) => {
const rootNode =
root.type === "NGRoot"
? root.node.type === "NGMicrosyntax" &&
root.node.body.length === 1 &&
root.node.body[0].type === "NGMicrosyntaxExpression"
? root.node.body[0].expression
: root.node
: root.type === "JsExpressionRoot"
? root.node
: root;
if (
rootNode &&
(rootNode.type === "ObjectExpression" ||
rootNode.type === "ArrayExpression" ||
(options.parser === "__vue_expression" &&
(rootNode.type === "TemplateLiteral" ||
rootNode.type === "StringLiteral")))
) {
shouldHug = true;
}
};
const printHug = (doc) => group(doc);
const printExpand = (doc, canHaveTrailingWhitespace = true) =>
group([indent([softline, doc]), canHaveTrailingWhitespace ? softline : ""]);
const printMaybeHug = (doc) => (shouldHug ? printHug(doc) : printExpand(doc));
const attributeTextToDoc = (code, opts) =>
originalTextToDoc(
code,
{ __onHtmlBindingRoot, __embeddedInHtml: true, ...opts },
{ stripTrailingHardline: true }
);
if (
node.fullName === "srcset" &&
(node.parent.fullName === "img" || node.parent.fullName === "source")
) {
return printExpand(printImgSrcset(getValue()));
}
if (node.fullName === "class" && !options.parentParser) {
const value = getValue();
if (!value.includes("{{")) {
return printClassNames(value);
}
}
if (node.fullName === "style" && !options.parentParser) {
const value = getValue();
if (!value.includes("{{")) {
return printExpand(
attributeTextToDoc(value, {
parser: "css",
__isHTMLStyleAttribute: true,
})
);
}
}
if (options.parser === "vue") {
if (node.fullName === "v-for") {
return printVueFor(getValue(), attributeTextToDoc);
}
if (isVueSlotAttribute(node) || isVueSfcBindingsAttribute(node, options)) {
return printVueBindings(getValue(), attributeTextToDoc);
}
/**
* @click="jsStatement"
* @click="jsExpression"
* v-on:click="jsStatement"
* v-on:click="jsExpression"
*/
const vueEventBindingPatterns = ["^@", "^v-on:"];
/**
* :class="vueExpression"
* v-bind:id="vueExpression"
*/
const vueExpressionBindingPatterns = ["^:", "^v-bind:"];
/**
* v-if="jsExpression"
*/
const jsExpressionBindingPatterns = ["^v-"];
if (isKeyMatched(vueEventBindingPatterns)) {
const value = getValue();
return printMaybeHug(
attributeTextToDoc(value, {
parser: isVueEventBindingExpression(value)
? "__js_expression"
: "__vue_event_binding",
})
);
}
if (isKeyMatched(vueExpressionBindingPatterns)) {
return printMaybeHug(
attributeTextToDoc(getValue(), { parser: "__vue_expression" })
);
}
if (isKeyMatched(jsExpressionBindingPatterns)) {
return printMaybeHug(
attributeTextToDoc(getValue(), { parser: "__js_expression" })
);
}
}
if (options.parser === "angular") {
const ngTextToDoc = (code, opts) =>
// angular does not allow trailing comma
attributeTextToDoc(code, { ...opts, trailingComma: "none" });
/**
* *directive="angularDirective"
*/
const ngDirectiveBindingPatterns = ["^\\*"];
/**
* (click)="angularStatement"
* on-click="angularStatement"
*/
const ngStatementBindingPatterns = ["^\\(.+\\)$", "^on-"];
/**
* [target]="angularExpression"
* bind-target="angularExpression"
* [(target)]="angularExpression"
* bindon-target="angularExpression"
*/
const ngExpressionBindingPatterns = [
"^\\[.+\\]$",
"^bind(on)?-",
// Unofficial rudimentary support for some of the most used directives of AngularJS 1.x
"^ng-(if|show|hide|class|style)$",
];
/**
* i18n="longDescription"
* i18n-attr="longDescription"
*/
const ngI18nPatterns = ["^i18n(-.+)?$"];
if (isKeyMatched(ngStatementBindingPatterns)) {
return printMaybeHug(ngTextToDoc(getValue(), { parser: "__ng_action" }));
}
if (isKeyMatched(ngExpressionBindingPatterns)) {
return printMaybeHug(ngTextToDoc(getValue(), { parser: "__ng_binding" }));
}
if (isKeyMatched(ngI18nPatterns)) {
const value = getValue().trim();
return printExpand(
fill(getTextValueParts(node, value)),
!value.includes("@@")
);
}
if (isKeyMatched(ngDirectiveBindingPatterns)) {
return printMaybeHug(
ngTextToDoc(getValue(), { parser: "__ng_directive" })
);
}
const interpolationRegex = /{{(.+?)}}/s;
const value = getValue();
if (interpolationRegex.test(value)) {
const parts = [];
for (const [index, part] of value.split(interpolationRegex).entries()) {
if (index % 2 === 0) {
parts.push(replaceTextEndOfLine(part));
} else {
try {
parts.push(
group([
"{{",
indent([
line,
ngTextToDoc(part, {
parser: "__ng_interpolation",
__isInHtmlInterpolation: true, // to avoid unexpected `}}`
}),
]),
line,
"}}",
])
);
} catch {
parts.push("{{", replaceTextEndOfLine(part), "}}");
}
}
}
return group(parts);
}
}
return null;
}
function embed(path, print, textToDoc, options) {
const node = path.getValue();
switch (node.type) {
case "element": {
if (isScriptLikeTag(node) || node.type === "interpolation") {
// Fall through to "text"
return;
}
if (!node.isSelfClosing && isVueNonHtmlBlock(node, options)) {
const parser = inferScriptParser(node, options);
if (!parser) {
return;
}
const content = getNodeContent(node, options);
let isEmpty = /^\s*$/.test(content);
let doc = "";
if (!isEmpty) {
doc = textToDoc(
htmlTrimPreserveIndentation(content),
{ parser, __embeddedInHtml: true },
{ stripTrailingHardline: true }
);
isEmpty = doc === "";
}
return [
printOpeningTagPrefix(node, options),
group(printOpeningTag(path, options, print)),
isEmpty ? "" : hardline,
doc,
isEmpty ? "" : hardline,
printClosingTag(node, options),
printClosingTagSuffix(node, options),
];
}
break;
}
case "text": {
if (isScriptLikeTag(node.parent)) {
const parser = inferScriptParser(node.parent);
if (parser) {
const value =
parser === "markdown"
? dedentString(node.value.replace(/^[^\S\n]*?\n/, ""))
: node.value;
const textToDocOptions = { parser, __embeddedInHtml: true };
if (options.parser === "html" && parser === "babel") {
let sourceType = "script";
const { attrMap } = node.parent;
if (
attrMap &&
(attrMap.type === "module" ||
(attrMap.type === "text/babel" &&
attrMap["data-type"] === "module"))
) {
sourceType = "module";
}
textToDocOptions.__babelSourceType = sourceType;
}
return [
breakParent,
printOpeningTagPrefix(node, options),
textToDoc(value, textToDocOptions, {
stripTrailingHardline: true,
}),
printClosingTagSuffix(node, options),
];
}
} else if (node.parent.type === "interpolation") {
const textToDocOptions = {
__isInHtmlInterpolation: true, // to avoid unexpected `}}`
__embeddedInHtml: true,
};
if (options.parser === "angular") {
textToDocOptions.parser = "__ng_interpolation";
textToDocOptions.trailingComma = "none";
} else if (options.parser === "vue") {
textToDocOptions.parser = "__vue_expression";
} else {
textToDocOptions.parser = "__js_expression";
}
return [
indent([
line,
textToDoc(node.value, textToDocOptions, {
stripTrailingHardline: true,
}),
]),
node.parent.next &&
needsToBorrowPrevClosingTagEndMarker(node.parent.next)
? " "
: line,
];
}
break;
}
case "attribute": {
if (!node.value) {
break;
}
// lit-html: html`<my-element obj=${obj}></my-element>`
if (
/^PRETTIER_HTML_PLACEHOLDER_\d+_\d+_IN_JS$/.test(
options.originalText.slice(
node.valueSpan.start.offset,
node.valueSpan.end.offset
)
)
) {
return [node.rawName, "=", node.value];
}
// lwc: html`<my-element data-for={value}></my-element>`
if (options.parser === "lwc") {
const interpolationRegex = /^{.*}$/s;
if (
interpolationRegex.test(
options.originalText.slice(
node.valueSpan.start.offset,
node.valueSpan.end.offset
)
)
) {
return [node.rawName, "=", node.value];
}
}
const embeddedAttributeValueDoc = printEmbeddedAttributeValue(
node,
(code, opts) =>
// strictly prefer single quote to avoid unnecessary html entity escape
textToDoc(
code,
{ __isInHtmlAttribute: true, __embeddedInHtml: true, ...opts },
{ stripTrailingHardline: true }
),
options
);
if (embeddedAttributeValueDoc) {
return [
node.rawName,
'="',
group(
mapDoc(embeddedAttributeValueDoc, (doc) =>
typeof doc === "string" ? doc.replace(/"/g, "&quot;") : doc
)
),
'"',
];
}
break;
}
case "front-matter":
return printFrontMatter(node, textToDoc);
}
}
module.exports = embed;
+34
View File
@@ -0,0 +1,34 @@
"use strict";
const {
needsToBorrowParentClosingTagStartMarker,
printClosingTagStartMarker,
needsToBorrowLastChildClosingTagEndMarker,
printClosingTagEndMarker,
needsToBorrowParentOpeningTagEndMarker,
printOpeningTagEndMarker,
} = require("./print/tag.js");
function getNodeContent(node, options) {
let start = node.startSourceSpan.end.offset;
if (
node.firstChild &&
needsToBorrowParentOpeningTagEndMarker(node.firstChild)
) {
start -= printOpeningTagEndMarker(node).length;
}
let end = node.endSourceSpan.start.offset;
if (
node.lastChild &&
needsToBorrowParentClosingTagStartMarker(node.lastChild)
) {
end += printClosingTagStartMarker(node, options).length;
} else if (needsToBorrowLastChildClosingTagEndMarker(node)) {
end -= printClosingTagEndMarker(node.lastChild, options).length;
}
return options.originalText.slice(start, end);
}
module.exports = getNodeContent;
+381
View File
@@ -0,0 +1,381 @@
"use strict";
/**
* @typedef {import("../../document").Doc} Doc
*/
const assert = require("assert");
const { isNonEmptyArray } = require("../../common/util.js");
const {
builders: { indent, join, line, softline },
utils: { replaceTextEndOfLine },
} = require("../../document/index.js");
const { locStart, locEnd } = require("../loc.js");
const {
isTextLikeNode,
getLastDescendant,
isPreLikeNode,
hasPrettierIgnore,
shouldPreserveContent,
} = require("../utils.js");
function printClosingTag(node, options) {
return [
node.isSelfClosing ? "" : printClosingTagStart(node, options),
printClosingTagEnd(node, options),
];
}
function printClosingTagStart(node, options) {
return node.lastChild &&
needsToBorrowParentClosingTagStartMarker(node.lastChild)
? ""
: [
printClosingTagPrefix(node, options),
printClosingTagStartMarker(node, options),
];
}
function printClosingTagEnd(node, options) {
return (
node.next
? needsToBorrowPrevClosingTagEndMarker(node.next)
: needsToBorrowLastChildClosingTagEndMarker(node.parent)
)
? ""
: [
printClosingTagEndMarker(node, options),
printClosingTagSuffix(node, options),
];
}
function printClosingTagPrefix(node, options) {
return needsToBorrowLastChildClosingTagEndMarker(node)
? printClosingTagEndMarker(node.lastChild, options)
: "";
}
function printClosingTagSuffix(node, options) {
return needsToBorrowParentClosingTagStartMarker(node)
? printClosingTagStartMarker(node.parent, options)
: needsToBorrowNextOpeningTagStartMarker(node)
? printOpeningTagStartMarker(node.next)
: "";
}
function printClosingTagStartMarker(node, options) {
assert(!node.isSelfClosing);
/* istanbul ignore next */
if (shouldNotPrintClosingTag(node, options)) {
return "";
}
switch (node.type) {
case "ieConditionalComment":
return "<!";
case "element":
if (node.hasHtmComponentClosingTag) {
return "<//";
}
// fall through
default:
return `</${node.rawName}`;
}
}
function printClosingTagEndMarker(node, options) {
if (shouldNotPrintClosingTag(node, options)) {
return "";
}
switch (node.type) {
case "ieConditionalComment":
case "ieConditionalEndComment":
return "[endif]-->";
case "ieConditionalStartComment":
return "]><!-->";
case "interpolation":
return "}}";
case "element":
if (node.isSelfClosing) {
return "/>";
}
// fall through
default:
return ">";
}
}
function shouldNotPrintClosingTag(node, options) {
return (
!node.isSelfClosing &&
!node.endSourceSpan &&
(hasPrettierIgnore(node) || shouldPreserveContent(node.parent, options))
);
}
function needsToBorrowPrevClosingTagEndMarker(node) {
/**
* <p></p
* >123
* ^
*
* <p></p
* ><a
* ^
*/
return (
node.prev &&
node.prev.type !== "docType" &&
!isTextLikeNode(node.prev) &&
node.isLeadingSpaceSensitive &&
!node.hasLeadingSpaces
);
}
function needsToBorrowLastChildClosingTagEndMarker(node) {
/**
* <p
* ><a></a
* ></p
* ^
* >
*/
return (
node.lastChild &&
node.lastChild.isTrailingSpaceSensitive &&
!node.lastChild.hasTrailingSpaces &&
!isTextLikeNode(getLastDescendant(node.lastChild)) &&
!isPreLikeNode(node)
);
}
function needsToBorrowParentClosingTagStartMarker(node) {
/**
* <p>
* 123</p
* ^^^
* >
*
* 123</b
* ></a
* ^^^
* >
*/
return (
!node.next &&
!node.hasTrailingSpaces &&
node.isTrailingSpaceSensitive &&
isTextLikeNode(getLastDescendant(node))
);
}
function needsToBorrowNextOpeningTagStartMarker(node) {
/**
* 123<p
* ^^
* >
*/
return (
node.next &&
!isTextLikeNode(node.next) &&
isTextLikeNode(node) &&
node.isTrailingSpaceSensitive &&
!node.hasTrailingSpaces
);
}
function getPrettierIgnoreAttributeCommentData(value) {
const match = value.trim().match(/^prettier-ignore-attribute(?:\s+(.+))?$/s);
if (!match) {
return false;
}
if (!match[1]) {
return true;
}
return match[1].split(/\s+/);
}
function needsToBorrowParentOpeningTagEndMarker(node) {
/**
* <p
* >123
* ^
*
* <p
* ><a
* ^
*/
return !node.prev && node.isLeadingSpaceSensitive && !node.hasLeadingSpaces;
}
function printAttributes(path, options, print) {
const node = path.getValue();
if (!isNonEmptyArray(node.attrs)) {
return node.isSelfClosing
? /**
* <br />
* ^
*/
" "
: "";
}
const ignoreAttributeData =
node.prev &&
node.prev.type === "comment" &&
getPrettierIgnoreAttributeCommentData(node.prev.value);
const hasPrettierIgnoreAttribute =
typeof ignoreAttributeData === "boolean"
? () => ignoreAttributeData
: Array.isArray(ignoreAttributeData)
? (attribute) => ignoreAttributeData.includes(attribute.rawName)
: () => false;
const printedAttributes = path.map((attributePath) => {
const attribute = attributePath.getValue();
return hasPrettierIgnoreAttribute(attribute)
? replaceTextEndOfLine(
options.originalText.slice(locStart(attribute), locEnd(attribute))
)
: print();
}, "attrs");
const forceNotToBreakAttrContent =
node.type === "element" &&
node.fullName === "script" &&
node.attrs.length === 1 &&
node.attrs[0].fullName === "src" &&
node.children.length === 0;
/** @type {Doc[]} */
const parts = [
indent([
forceNotToBreakAttrContent ? " " : line,
join(line, printedAttributes),
]),
];
if (
/**
* 123<a
* attr
* ~
* >456
*/
(node.firstChild &&
needsToBorrowParentOpeningTagEndMarker(node.firstChild)) ||
/**
* <span
* >123<meta
* ~
* /></span>
*/
(node.isSelfClosing &&
needsToBorrowLastChildClosingTagEndMarker(node.parent)) ||
forceNotToBreakAttrContent
) {
parts.push(node.isSelfClosing ? " " : "");
} else {
parts.push(
options.bracketSameLine
? node.isSelfClosing
? " "
: ""
: node.isSelfClosing
? line
: softline
);
}
return parts;
}
function printOpeningTagEnd(node) {
return node.firstChild &&
needsToBorrowParentOpeningTagEndMarker(node.firstChild)
? ""
: printOpeningTagEndMarker(node);
}
function printOpeningTag(path, options, print) {
const node = path.getValue();
return [
printOpeningTagStart(node, options),
printAttributes(path, options, print),
node.isSelfClosing ? "" : printOpeningTagEnd(node),
];
}
function printOpeningTagStart(node, options) {
return node.prev && needsToBorrowNextOpeningTagStartMarker(node.prev)
? ""
: [printOpeningTagPrefix(node, options), printOpeningTagStartMarker(node)];
}
function printOpeningTagPrefix(node, options) {
return needsToBorrowParentOpeningTagEndMarker(node)
? printOpeningTagEndMarker(node.parent)
: needsToBorrowPrevClosingTagEndMarker(node)
? printClosingTagEndMarker(node.prev, options)
: "";
}
function printOpeningTagStartMarker(node) {
switch (node.type) {
case "ieConditionalComment":
case "ieConditionalStartComment":
return `<!--[if ${node.condition}`;
case "ieConditionalEndComment":
return "<!--<!";
case "interpolation":
return "{{";
case "docType":
return "<!DOCTYPE";
case "element":
if (node.condition) {
return `<!--[if ${node.condition}]><!--><${node.rawName}`;
}
// fall through
default:
return `<${node.rawName}`;
}
}
function printOpeningTagEndMarker(node) {
assert(!node.isSelfClosing);
switch (node.type) {
case "ieConditionalComment":
return "]>";
case "element":
if (node.condition) {
return "><!--<![endif]-->";
}
// fall through
default:
return ">";
}
}
module.exports = {
printClosingTag,
printClosingTagStart,
printClosingTagStartMarker,
printClosingTagEndMarker,
printClosingTagSuffix,
printClosingTagEnd,
needsToBorrowLastChildClosingTagEndMarker,
needsToBorrowParentClosingTagStartMarker,
needsToBorrowPrevClosingTagEndMarker,
printOpeningTag,
printOpeningTagStart,
printOpeningTagPrefix,
printOpeningTagStartMarker,
printOpeningTagEndMarker,
needsToBorrowNextOpeningTagStartMarker,
needsToBorrowParentOpeningTagEndMarker,
};
+17 -743
View File
@@ -4,8 +4,6 @@
* @typedef {import("../document").Doc} Doc
*/
const assert = require("assert");
const {
builders: {
breakParent,
@@ -16,211 +14,47 @@ const {
ifBreak,
indentIfBreak,
indent,
join,
line,
literalline,
softline,
},
utils: { mapDoc, cleanDoc, getDocParts, isConcat, replaceTextEndOfLine },
utils: { cleanDoc, getDocParts, isConcat, replaceTextEndOfLine },
} = require("../document/index.js");
const { isNonEmptyArray } = require("../common/util.js");
const printFrontMatter = require("../utils/front-matter/print.js");
const clean = require("./clean.js");
const {
htmlTrimPreserveIndentation,
splitByHtmlWhitespace,
countChars,
countParents,
dedentString,
forceBreakChildren,
forceBreakContent,
forceNextEmptyLine,
getLastDescendant,
getPrettierIgnoreAttributeCommentData,
hasPrettierIgnore,
inferScriptParser,
isVueCustomBlock,
isVueNonHtmlBlock,
isVueSlotAttribute,
isVueSfcBindingsAttribute,
isScriptLikeTag,
isTextLikeNode,
preferHardlineAsLeadingSpaces,
shouldNotPrintClosingTag,
shouldPreserveContent,
unescapeQuoteEntities,
isPreLikeNode,
getTextValueParts,
} = require("./utils.js");
const preprocess = require("./print-preprocess.js");
const { insertPragma } = require("./pragma.js");
const { locStart, locEnd } = require("./loc.js");
const embed = require("./embed.js");
const {
printVueFor,
printVueBindings,
isVueEventBindingExpression,
} = require("./syntax-vue.js");
const { printImgSrcset, printClassNames } = require("./syntax-attribute.js");
function embed(path, print, textToDoc, options) {
const node = path.getValue();
switch (node.type) {
case "element": {
if (isScriptLikeTag(node) || node.type === "interpolation") {
// Fall through to "text"
return;
}
if (!node.isSelfClosing && isVueNonHtmlBlock(node, options)) {
const parser = inferScriptParser(node, options);
if (!parser) {
return;
}
const content = getNodeContent(node, options);
let isEmpty = /^\s*$/.test(content);
let doc = "";
if (!isEmpty) {
doc = textToDoc(
htmlTrimPreserveIndentation(content),
{ parser, __embeddedInHtml: true },
{ stripTrailingHardline: true }
);
isEmpty = doc === "";
}
return [
printOpeningTagPrefix(node, options),
group(printOpeningTag(path, options, print)),
isEmpty ? "" : hardline,
doc,
isEmpty ? "" : hardline,
printClosingTag(node, options),
printClosingTagSuffix(node, options),
];
}
break;
}
case "text": {
if (isScriptLikeTag(node.parent)) {
const parser = inferScriptParser(node.parent);
if (parser) {
const value =
parser === "markdown"
? dedentString(node.value.replace(/^[^\S\n]*?\n/, ""))
: node.value;
const textToDocOptions = { parser, __embeddedInHtml: true };
if (options.parser === "html" && parser === "babel") {
let sourceType = "script";
const { attrMap } = node.parent;
if (
attrMap &&
(attrMap.type === "module" ||
(attrMap.type === "text/babel" &&
attrMap["data-type"] === "module"))
) {
sourceType = "module";
}
textToDocOptions.__babelSourceType = sourceType;
}
return [
breakParent,
printOpeningTagPrefix(node, options),
textToDoc(value, textToDocOptions, {
stripTrailingHardline: true,
}),
printClosingTagSuffix(node, options),
];
}
} else if (node.parent.type === "interpolation") {
const textToDocOptions = {
__isInHtmlInterpolation: true, // to avoid unexpected `}}`
__embeddedInHtml: true,
};
if (options.parser === "angular") {
textToDocOptions.parser = "__ng_interpolation";
textToDocOptions.trailingComma = "none";
} else if (options.parser === "vue") {
textToDocOptions.parser = "__vue_expression";
} else {
textToDocOptions.parser = "__js_expression";
}
return [
indent([
line,
textToDoc(node.value, textToDocOptions, {
stripTrailingHardline: true,
}),
]),
node.parent.next &&
needsToBorrowPrevClosingTagEndMarker(node.parent.next)
? " "
: line,
];
}
break;
}
case "attribute": {
if (!node.value) {
break;
}
// lit-html: html`<my-element obj=${obj}></my-element>`
if (
/^PRETTIER_HTML_PLACEHOLDER_\d+_\d+_IN_JS$/.test(
options.originalText.slice(
node.valueSpan.start.offset,
node.valueSpan.end.offset
)
)
) {
return [node.rawName, "=", node.value];
}
// lwc: html`<my-element data-for={value}></my-element>`
if (options.parser === "lwc") {
const interpolationRegex = /^{.*}$/s;
if (
interpolationRegex.test(
options.originalText.slice(
node.valueSpan.start.offset,
node.valueSpan.end.offset
)
)
) {
return [node.rawName, "=", node.value];
}
}
const embeddedAttributeValueDoc = printEmbeddedAttributeValue(
node,
(code, opts) =>
// strictly prefer single quote to avoid unnecessary html entity escape
textToDoc(
code,
{ __isInHtmlAttribute: true, __embeddedInHtml: true, ...opts },
{ stripTrailingHardline: true }
),
options
);
if (embeddedAttributeValueDoc) {
return [
node.rawName,
'="',
group(
mapDoc(embeddedAttributeValueDoc, (doc) =>
typeof doc === "string" ? doc.replace(/"/g, "&quot;") : doc
)
),
'"',
];
}
break;
}
case "front-matter":
return printFrontMatter(node, textToDoc);
}
}
printClosingTagEndMarker,
printClosingTagSuffix,
printClosingTag,
printClosingTagEnd,
needsToBorrowPrevClosingTagEndMarker,
needsToBorrowLastChildClosingTagEndMarker,
needsToBorrowParentClosingTagStartMarker,
printOpeningTagPrefix,
printOpeningTag,
printOpeningTagStart,
printOpeningTagStartMarker,
needsToBorrowNextOpeningTagStartMarker,
} = require("./print/tag.js");
const getNodeContent = require("./get-node-content.js");
function genericPrint(path, options, print) {
const node = path.getValue();
@@ -648,566 +482,6 @@ function printChildren(path, options, print) {
}
}
function getNodeContent(node, options) {
let start = node.startSourceSpan.end.offset;
if (
node.firstChild &&
needsToBorrowParentOpeningTagEndMarker(node.firstChild)
) {
start -= printOpeningTagEndMarker(node).length;
}
let end = node.endSourceSpan.start.offset;
if (
node.lastChild &&
needsToBorrowParentClosingTagStartMarker(node.lastChild)
) {
end += printClosingTagStartMarker(node, options).length;
} else if (needsToBorrowLastChildClosingTagEndMarker(node)) {
end -= printClosingTagEndMarker(node.lastChild, options).length;
}
return options.originalText.slice(start, end);
}
function printAttributes(path, options, print) {
const node = path.getValue();
if (!isNonEmptyArray(node.attrs)) {
return node.isSelfClosing
? /**
* <br />
* ^
*/
" "
: "";
}
const ignoreAttributeData =
node.prev &&
node.prev.type === "comment" &&
getPrettierIgnoreAttributeCommentData(node.prev.value);
const hasPrettierIgnoreAttribute =
typeof ignoreAttributeData === "boolean"
? () => ignoreAttributeData
: Array.isArray(ignoreAttributeData)
? (attribute) => ignoreAttributeData.includes(attribute.rawName)
: () => false;
const printedAttributes = path.map((attributePath) => {
const attribute = attributePath.getValue();
return hasPrettierIgnoreAttribute(attribute)
? replaceTextEndOfLine(
options.originalText.slice(locStart(attribute), locEnd(attribute))
)
: print();
}, "attrs");
const forceNotToBreakAttrContent =
node.type === "element" &&
node.fullName === "script" &&
node.attrs.length === 1 &&
node.attrs[0].fullName === "src" &&
node.children.length === 0;
/** @type {Doc[]} */
const parts = [
indent([
forceNotToBreakAttrContent ? " " : line,
join(line, printedAttributes),
]),
];
if (
/**
* 123<a
* attr
* ~
* >456
*/
(node.firstChild &&
needsToBorrowParentOpeningTagEndMarker(node.firstChild)) ||
/**
* <span
* >123<meta
* ~
* /></span>
*/
(node.isSelfClosing &&
needsToBorrowLastChildClosingTagEndMarker(node.parent)) ||
forceNotToBreakAttrContent
) {
parts.push(node.isSelfClosing ? " " : "");
} else {
parts.push(
options.bracketSameLine
? node.isSelfClosing
? " "
: ""
: node.isSelfClosing
? line
: softline
);
}
return parts;
}
function printOpeningTag(path, options, print) {
const node = path.getValue();
return [
printOpeningTagStart(node, options),
printAttributes(path, options, print),
node.isSelfClosing ? "" : printOpeningTagEnd(node),
];
}
function printOpeningTagStart(node, options) {
return node.prev && needsToBorrowNextOpeningTagStartMarker(node.prev)
? ""
: [printOpeningTagPrefix(node, options), printOpeningTagStartMarker(node)];
}
function printOpeningTagEnd(node) {
return node.firstChild &&
needsToBorrowParentOpeningTagEndMarker(node.firstChild)
? ""
: printOpeningTagEndMarker(node);
}
function printClosingTag(node, options) {
return [
node.isSelfClosing ? "" : printClosingTagStart(node, options),
printClosingTagEnd(node, options),
];
}
function printClosingTagStart(node, options) {
return node.lastChild &&
needsToBorrowParentClosingTagStartMarker(node.lastChild)
? ""
: [
printClosingTagPrefix(node, options),
printClosingTagStartMarker(node, options),
];
}
function printClosingTagEnd(node, options) {
return (
node.next
? needsToBorrowPrevClosingTagEndMarker(node.next)
: needsToBorrowLastChildClosingTagEndMarker(node.parent)
)
? ""
: [
printClosingTagEndMarker(node, options),
printClosingTagSuffix(node, options),
];
}
function needsToBorrowNextOpeningTagStartMarker(node) {
/**
* 123<p
* ^^
* >
*/
return (
node.next &&
!isTextLikeNode(node.next) &&
isTextLikeNode(node) &&
node.isTrailingSpaceSensitive &&
!node.hasTrailingSpaces
);
}
function needsToBorrowParentOpeningTagEndMarker(node) {
/**
* <p
* >123
* ^
*
* <p
* ><a
* ^
*/
return !node.prev && node.isLeadingSpaceSensitive && !node.hasLeadingSpaces;
}
function needsToBorrowPrevClosingTagEndMarker(node) {
/**
* <p></p
* >123
* ^
*
* <p></p
* ><a
* ^
*/
return (
node.prev &&
node.prev.type !== "docType" &&
!isTextLikeNode(node.prev) &&
node.isLeadingSpaceSensitive &&
!node.hasLeadingSpaces
);
}
function needsToBorrowLastChildClosingTagEndMarker(node) {
/**
* <p
* ><a></a
* ></p
* ^
* >
*/
return (
node.lastChild &&
node.lastChild.isTrailingSpaceSensitive &&
!node.lastChild.hasTrailingSpaces &&
!isTextLikeNode(getLastDescendant(node.lastChild)) &&
!isPreLikeNode(node)
);
}
function needsToBorrowParentClosingTagStartMarker(node) {
/**
* <p>
* 123</p
* ^^^
* >
*
* 123</b
* ></a
* ^^^
* >
*/
return (
!node.next &&
!node.hasTrailingSpaces &&
node.isTrailingSpaceSensitive &&
isTextLikeNode(getLastDescendant(node))
);
}
function printOpeningTagPrefix(node, options) {
return needsToBorrowParentOpeningTagEndMarker(node)
? printOpeningTagEndMarker(node.parent)
: needsToBorrowPrevClosingTagEndMarker(node)
? printClosingTagEndMarker(node.prev, options)
: "";
}
function printClosingTagPrefix(node, options) {
return needsToBorrowLastChildClosingTagEndMarker(node)
? printClosingTagEndMarker(node.lastChild, options)
: "";
}
function printClosingTagSuffix(node, options) {
return needsToBorrowParentClosingTagStartMarker(node)
? printClosingTagStartMarker(node.parent, options)
: needsToBorrowNextOpeningTagStartMarker(node)
? printOpeningTagStartMarker(node.next)
: "";
}
function printOpeningTagStartMarker(node) {
switch (node.type) {
case "ieConditionalComment":
case "ieConditionalStartComment":
return `<!--[if ${node.condition}`;
case "ieConditionalEndComment":
return "<!--<!";
case "interpolation":
return "{{";
case "docType":
return "<!DOCTYPE";
case "element":
if (node.condition) {
return `<!--[if ${node.condition}]><!--><${node.rawName}`;
}
// fall through
default:
return `<${node.rawName}`;
}
}
function printOpeningTagEndMarker(node) {
assert(!node.isSelfClosing);
switch (node.type) {
case "ieConditionalComment":
return "]>";
case "element":
if (node.condition) {
return "><!--<![endif]-->";
}
// fall through
default:
return ">";
}
}
function printClosingTagStartMarker(node, options) {
assert(!node.isSelfClosing);
/* istanbul ignore next */
if (shouldNotPrintClosingTag(node, options)) {
return "";
}
switch (node.type) {
case "ieConditionalComment":
return "<!";
case "element":
if (node.hasHtmComponentClosingTag) {
return "<//";
}
// fall through
default:
return `</${node.rawName}`;
}
}
function printClosingTagEndMarker(node, options) {
if (shouldNotPrintClosingTag(node, options)) {
return "";
}
switch (node.type) {
case "ieConditionalComment":
case "ieConditionalEndComment":
return "[endif]-->";
case "ieConditionalStartComment":
return "]><!-->";
case "interpolation":
return "}}";
case "element":
if (node.isSelfClosing) {
return "/>";
}
// fall through
default:
return ">";
}
}
function getTextValueParts(node, value = node.value) {
return node.parent.isWhitespaceSensitive
? node.parent.isIndentationSensitive
? replaceTextEndOfLine(value)
: replaceTextEndOfLine(
dedentString(htmlTrimPreserveIndentation(value)),
hardline
)
: getDocParts(join(line, splitByHtmlWhitespace(value)));
}
function printEmbeddedAttributeValue(node, originalTextToDoc, options) {
const isKeyMatched = (patterns) =>
new RegExp(patterns.join("|")).test(node.fullName);
const getValue = () => unescapeQuoteEntities(node.value);
let shouldHug = false;
const __onHtmlBindingRoot = (root, options) => {
const rootNode =
root.type === "NGRoot"
? root.node.type === "NGMicrosyntax" &&
root.node.body.length === 1 &&
root.node.body[0].type === "NGMicrosyntaxExpression"
? root.node.body[0].expression
: root.node
: root.type === "JsExpressionRoot"
? root.node
: root;
if (
rootNode &&
(rootNode.type === "ObjectExpression" ||
rootNode.type === "ArrayExpression" ||
(options.parser === "__vue_expression" &&
(rootNode.type === "TemplateLiteral" ||
rootNode.type === "StringLiteral")))
) {
shouldHug = true;
}
};
const printHug = (doc) => group(doc);
const printExpand = (doc, canHaveTrailingWhitespace = true) =>
group([indent([softline, doc]), canHaveTrailingWhitespace ? softline : ""]);
const printMaybeHug = (doc) => (shouldHug ? printHug(doc) : printExpand(doc));
const attributeTextToDoc = (code, opts) =>
originalTextToDoc(
code,
{ __onHtmlBindingRoot, __embeddedInHtml: true, ...opts },
{ stripTrailingHardline: true }
);
if (
node.fullName === "srcset" &&
(node.parent.fullName === "img" || node.parent.fullName === "source")
) {
return printExpand(printImgSrcset(getValue()));
}
if (node.fullName === "class" && !options.parentParser) {
const value = getValue();
if (!value.includes("{{")) {
return printClassNames(value);
}
}
if (node.fullName === "style" && !options.parentParser) {
const value = getValue();
if (!value.includes("{{")) {
return printExpand(
attributeTextToDoc(value, {
parser: "css",
__isHTMLStyleAttribute: true,
})
);
}
}
if (options.parser === "vue") {
if (node.fullName === "v-for") {
return printVueFor(getValue(), attributeTextToDoc);
}
if (isVueSlotAttribute(node) || isVueSfcBindingsAttribute(node, options)) {
return printVueBindings(getValue(), attributeTextToDoc);
}
/**
* @click="jsStatement"
* @click="jsExpression"
* v-on:click="jsStatement"
* v-on:click="jsExpression"
*/
const vueEventBindingPatterns = ["^@", "^v-on:"];
/**
* :class="vueExpression"
* v-bind:id="vueExpression"
*/
const vueExpressionBindingPatterns = ["^:", "^v-bind:"];
/**
* v-if="jsExpression"
*/
const jsExpressionBindingPatterns = ["^v-"];
if (isKeyMatched(vueEventBindingPatterns)) {
const value = getValue();
return printMaybeHug(
attributeTextToDoc(value, {
parser: isVueEventBindingExpression(value)
? "__js_expression"
: "__vue_event_binding",
})
);
}
if (isKeyMatched(vueExpressionBindingPatterns)) {
return printMaybeHug(
attributeTextToDoc(getValue(), { parser: "__vue_expression" })
);
}
if (isKeyMatched(jsExpressionBindingPatterns)) {
return printMaybeHug(
attributeTextToDoc(getValue(), { parser: "__js_expression" })
);
}
}
if (options.parser === "angular") {
const ngTextToDoc = (code, opts) =>
// angular does not allow trailing comma
attributeTextToDoc(code, { ...opts, trailingComma: "none" });
/**
* *directive="angularDirective"
*/
const ngDirectiveBindingPatterns = ["^\\*"];
/**
* (click)="angularStatement"
* on-click="angularStatement"
*/
const ngStatementBindingPatterns = ["^\\(.+\\)$", "^on-"];
/**
* [target]="angularExpression"
* bind-target="angularExpression"
* [(target)]="angularExpression"
* bindon-target="angularExpression"
*/
const ngExpressionBindingPatterns = [
"^\\[.+\\]$",
"^bind(on)?-",
// Unofficial rudimentary support for some of the most used directives of AngularJS 1.x
"^ng-(if|show|hide|class|style)$",
];
/**
* i18n="longDescription"
* i18n-attr="longDescription"
*/
const ngI18nPatterns = ["^i18n(-.+)?$"];
if (isKeyMatched(ngStatementBindingPatterns)) {
return printMaybeHug(ngTextToDoc(getValue(), { parser: "__ng_action" }));
}
if (isKeyMatched(ngExpressionBindingPatterns)) {
return printMaybeHug(ngTextToDoc(getValue(), { parser: "__ng_binding" }));
}
if (isKeyMatched(ngI18nPatterns)) {
const value = getValue().trim();
return printExpand(
fill(getTextValueParts(node, value)),
!value.includes("@@")
);
}
if (isKeyMatched(ngDirectiveBindingPatterns)) {
return printMaybeHug(
ngTextToDoc(getValue(), { parser: "__ng_directive" })
);
}
const interpolationRegex = /{{(.+?)}}/s;
const value = getValue();
if (interpolationRegex.test(value)) {
const parts = [];
for (const [index, part] of value.split(interpolationRegex).entries()) {
if (index % 2 === 0) {
parts.push(replaceTextEndOfLine(part));
} else {
try {
parts.push(
group([
"{{",
indent([
line,
ngTextToDoc(part, {
parser: "__ng_interpolation",
__isInHtmlInterpolation: true, // to avoid unexpected `}}`
}),
]),
line,
"}}",
])
);
} catch {
parts.push("{{", replaceTextEndOfLine(part), "}}");
}
}
}
return group(parts);
}
}
return null;
}
module.exports = {
preprocess,
print: genericPrint,
+16 -25
View File
@@ -10,6 +10,10 @@ const {
inferParserByLanguage,
isFrontMatterNode,
} = require("../common/util.js");
const {
builders: { line, hardline, join },
utils: { getDocParts, replaceTextEndOfLine },
} = require("../document/index.js");
const {
CSS_DISPLAY_TAGS,
CSS_DISPLAY_DEFAULT,
@@ -121,20 +125,6 @@ function isPrettierIgnore(node) {
return node.type === "comment" && node.value.trim() === "prettier-ignore";
}
function getPrettierIgnoreAttributeCommentData(value) {
const match = value.trim().match(/^prettier-ignore-attribute(?:\s+(.+))?$/s);
if (!match) {
return false;
}
if (!match[1]) {
return true;
}
return match[1].split(/\s+/);
}
/** there's no opening/closing tag or it's considered not breakable */
function isTextLikeNode(node) {
return node.type === "text" || node.type === "comment";
@@ -602,14 +592,6 @@ function dedentString(text, minIndent = getMinIndentation(text)) {
.join("\n");
}
function shouldNotPrintClosingTag(node, options) {
return (
!node.isSelfClosing &&
!node.endSourceSpan &&
(hasPrettierIgnore(node) || shouldPreserveContent(node.parent, options))
);
}
function countChars(text, char) {
let counter = 0;
for (let i = 0; i < text.length; i++) {
@@ -674,12 +656,22 @@ function isVueSfcBindingsAttribute(attribute, options) {
);
}
function getTextValueParts(node, value = node.value) {
return node.parent.isWhitespaceSensitive
? node.parent.isIndentationSensitive
? replaceTextEndOfLine(value)
: replaceTextEndOfLine(
dedentString(htmlTrimPreserveIndentation(value)),
hardline
)
: getDocParts(join(line, splitByHtmlWhitespace(value)));
}
module.exports = {
HTML_ELEMENT_ATTRIBUTES,
HTML_TAGS,
htmlTrim,
htmlTrimPreserveIndentation,
splitByHtmlWhitespace,
hasHtmlWhitespace,
getLeadingAndTrailingHtmlWhitespace,
canHaveInterpolation,
@@ -692,7 +684,6 @@ module.exports = {
getLastDescendant,
getNodeCssStyleDisplay,
getNodeCssStyleWhiteSpace,
getPrettierIgnoreAttributeCommentData,
hasPrettierIgnore,
inferScriptParser,
isVueCustomBlock,
@@ -710,7 +701,7 @@ module.exports = {
isUnknownNamespace,
preferHardlineAsLeadingSpaces,
preferHardlineAsTrailingSpaces,
shouldNotPrintClosingTag,
shouldPreserveContent,
unescapeQuoteEntities,
getTextValueParts,
};