diff --git a/changelog_unreleased/vue/pr-8023.md b/changelog_unreleased/vue/pr-8023.md
new file mode 100644
index 000000000..894a93124
--- /dev/null
+++ b/changelog_unreleased/vue/pr-8023.md
@@ -0,0 +1,27 @@
+#### Support custom blocks ([#8023](https://github.com/prettier/prettier/pull/8023) by [@sosukesuzuki](https://github.com/sosukesuzuki))
+
+Support [vue-loader custom blocks](https://vue-loader.vuejs.org/guide/custom-blocks.html) in SFC with `lang` attribute.
+
+
+```html
+
+
+{
+ "foo":
+"bar"}
+
+
+
+
+{
+ "foo":
+"bar"}
+
+
+
+
+{
+ "foo": "bar"
+}
+
+```
diff --git a/src/common/util.js b/src/common/util.js
index 0de284663..f398e76e7 100644
--- a/src/common/util.js
+++ b/src/common/util.js
@@ -3,6 +3,7 @@
const stringWidth = require("string-width");
const escapeStringRegexp = require("escape-string-regexp");
const getLast = require("../utils/get-last");
+const support = require("../main/support");
// eslint-disable-next-line no-control-regex
const notAsciiRegex = /[^\x20-\x7F]/;
@@ -820,6 +821,22 @@ function replaceEndOfLineWith(text, replacement) {
return parts;
}
+function getParserName(lang, options) {
+ const supportInfo = support.getSupportInfo({ plugins: options.plugins });
+ const language = supportInfo.languages.find(
+ (language) =>
+ language.name.toLowerCase() === lang ||
+ (language.aliases && language.aliases.includes(lang)) ||
+ (language.extensions &&
+ language.extensions.find((ext) => ext === `.${lang}`))
+ );
+ if (language) {
+ return language.parsers[0];
+ }
+
+ return null;
+}
+
module.exports = {
replaceEndOfLineWith,
getStringWidth,
@@ -828,6 +845,7 @@ module.exports = {
getPrecedence,
shouldFlatten,
isBitwiseOperator,
+ getParserName,
getPenultimate,
getLast,
getNextNonSpaceNonCommentCharacterIndexWithStartIndex,
diff --git a/src/document/doc-utils.js b/src/document/doc-utils.js
index 27afc174b..70e824d9c 100644
--- a/src/document/doc-utils.js
+++ b/src/document/doc-utils.js
@@ -182,17 +182,31 @@ function removeLines(doc) {
return mapDoc(doc, removeLinesFn);
}
-function stripTrailingHardline(doc) {
+function getInnerParts(doc) {
+ let { parts } = doc;
+ let lastPart;
+ // Avoid a falsy element like ""
+ for (let i = doc.parts.length; i > 0 && !lastPart; i--) {
+ lastPart = parts[i - 1];
+ }
+ if (lastPart.type === "group") {
+ parts = lastPart.contents.parts;
+ }
+ return parts;
+}
+
+function stripTrailingHardline(doc, withInnerParts = false) {
// HACK remove ending hardline, original PR: #1984
if (doc.type === "concat" && doc.parts.length !== 0) {
- const lastPart = doc.parts[doc.parts.length - 1];
+ const parts = withInnerParts ? getInnerParts(doc) : doc.parts;
+ const lastPart = parts[parts.length - 1];
if (lastPart.type === "concat") {
if (
lastPart.parts.length === 2 &&
lastPart.parts[0].hard &&
lastPart.parts[1].type === "break-parent"
) {
- return { type: "concat", parts: doc.parts.slice(0, -1) };
+ return { type: "concat", parts: parts.slice(0, -1) };
}
return {
diff --git a/src/language-html/preprocess.js b/src/language-html/preprocess.js
index 956b82d43..81305da69 100644
--- a/src/language-html/preprocess.js
+++ b/src/language-html/preprocess.js
@@ -426,7 +426,7 @@ function addCssDisplay(ast, options) {
* - add `isTrailingSpaceSensitive` field
* - add `isDanglingSpaceSensitive` field for parent nodes
*/
-function addIsSpaceSensitive(ast /*, options */) {
+function addIsSpaceSensitive(ast, options) {
return ast.map((node) => {
if (!node.children) {
return node;
@@ -443,8 +443,14 @@ function addIsSpaceSensitive(ast /*, options */) {
.map((child) => {
return {
...child,
- isLeadingSpaceSensitive: isLeadingSpaceSensitiveNode(child),
- isTrailingSpaceSensitive: isTrailingSpaceSensitiveNode(child),
+ isLeadingSpaceSensitive: isLeadingSpaceSensitiveNode(
+ child,
+ options
+ ),
+ isTrailingSpaceSensitive: isTrailingSpaceSensitiveNode(
+ child,
+ options
+ ),
};
})
.map((child, index, children) => ({
diff --git a/src/language-html/printer-html.js b/src/language-html/printer-html.js
index 8baaaff21..7479997e6 100644
--- a/src/language-html/printer-html.js
+++ b/src/language-html/printer-html.js
@@ -30,6 +30,7 @@ const {
getPrettierIgnoreAttributeCommentData,
hasPrettierIgnore,
inferScriptParser,
+ isVueCustomBlock,
isScriptLikeTag,
isTextLikeNode,
normalizeParts,
@@ -98,6 +99,25 @@ function embed(path, print, textToDoc, options) {
? " "
: line,
]);
+ } else if (isVueCustomBlock(node.parent, options)) {
+ const parser = inferScriptParser(node.parent, options);
+ let printed;
+ if (parser) {
+ try {
+ printed = textToDoc(node.value, { parser });
+ } catch (error) {
+ // Do nothing
+ }
+ }
+ if (printed == null) {
+ printed = node.value;
+ }
+ return concat([
+ parser ? breakParent : "",
+ printOpeningTagPrefix(node),
+ stripTrailingHardline(printed, true),
+ printClosingTagSuffix(node),
+ ]);
}
break;
}
@@ -225,7 +245,8 @@ function genericPrint(path, options, print) {
? ifBreak(indent(childrenDoc), childrenDoc, {
groupId: attrGroupId,
})
- : isScriptLikeTag(node) &&
+ : (isScriptLikeTag(node) ||
+ isVueCustomBlock(node, options)) &&
node.parent.type === "root" &&
options.parser === "vue" &&
!options.vueIndentScriptAndStyle
diff --git a/src/language-html/utils.js b/src/language-html/utils.js
index 01e9ecd5f..04df58f85 100644
--- a/src/language-html/utils.js
+++ b/src/language-html/utils.js
@@ -6,6 +6,7 @@ const {
CSS_WHITE_SPACE_TAGS,
CSS_WHITE_SPACE_DEFAULT,
} = require("./constants.evaluate");
+const { getParserName } = require("../common/util");
const htmlTagNames = require("html-tag-names");
const htmlElementAttributes = require("html-element-attributes");
@@ -60,23 +61,6 @@ function shouldPreserveContent(node, options) {
return true;
}
- // top-level elements (excluding ,