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