From 915327d61d3da3a9d8cd8b3010d6fce7ef49ae85 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Fri, 17 Apr 2020 07:54:24 +0900 Subject: [PATCH] Vue: Support custom blocks (#8023) * Support Vue Custom Block * Add changelog * Udpate snapshots * Support "type" for json * Remove guessing languages * Modify to reuse inferScriptParser logic * Implement withInnerParts arg for stripTrailingHardline * Rename isVueCustomBlock * Set `verbose: true` * Use getParserName * Ignore vueIndentScriptAndStyle * tmp * Fix formatting * Add tests * Fix by lint * Revert "Set `verbose: true`" This reverts commit f65c7cd7d814d3149aadbeb3cfd00c5372887c33. * loop with i-- * Modify to preserve unknown langauge * Support embeddedLanguageFormatting * Fix by lint * Fix mistake * Modify to preserve unknown lang * Remove unnecesary condition * Add error tests Co-authored-by: fisker Cheung --- changelog_unreleased/vue/pr-8023.md | 27 + src/common/util.js | 18 + src/document/doc-utils.js | 20 +- src/language-html/preprocess.js | 12 +- src/language-html/printer-html.js | 23 +- src/language-html/utils.js | 145 +- src/language-markdown/embed.js | 19 +- .../vue/__snapshots__/jsfmt.spec.js.snap | 55 + tests/errors/vue/custom_block.vue | 3 + tests/errors/vue/jsfmt.spec.js | 6 + .../__snapshots__/jsfmt.spec.js.snap | 1513 +++++++++++++++++ tests/html_vue_custom_block/graphql.vue | 11 + tests/html_vue_custom_block/handlebars.vue | 3 + tests/html_vue_custom_block/jsfmt.spec.js | 5 + tests/html_vue_custom_block/json.vue | 34 + .../html_vue_custom_block/lang-attribute.vue | 4 + tests/html_vue_custom_block/markdown.vue | 23 + tests/html_vue_custom_block/unknown.vue | 25 + tests/html_vue_custom_block/yaml.vue | 6 + 19 files changed, 1862 insertions(+), 90 deletions(-) create mode 100644 changelog_unreleased/vue/pr-8023.md create mode 100644 tests/errors/vue/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/errors/vue/custom_block.vue create mode 100644 tests/errors/vue/jsfmt.spec.js create mode 100644 tests/html_vue_custom_block/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/html_vue_custom_block/graphql.vue create mode 100644 tests/html_vue_custom_block/handlebars.vue create mode 100644 tests/html_vue_custom_block/jsfmt.spec.js create mode 100644 tests/html_vue_custom_block/json.vue create mode 100644 tests/html_vue_custom_block/lang-attribute.vue create mode 100644 tests/html_vue_custom_block/markdown.vue create mode 100644 tests/html_vue_custom_block/unknown.vue create mode 100644 tests/html_vue_custom_block/yaml.vue 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