Merge branch 'main' into next
This commit is contained in:
7 files changed
+854
-772
No files matched your search
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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, """) : doc
|
||||
)
|
||||
),
|
||||
'"',
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "front-matter":
|
||||
return printFrontMatter(node, textToDoc);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = embed;
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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, """) : 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
@@ -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,
|
||||
};
|
||||
Reference in new issue
Block a user