diff --git a/changelog_unreleased/vue/14506.md b/changelog_unreleased/vue/14506.md new file mode 100644 index 000000000..c797ccd15 --- /dev/null +++ b/changelog_unreleased/vue/14506.md @@ -0,0 +1,22 @@ +#### Format TypeScript expression in attribute bindings (#14506 by @seiyab) + + +```vue + + + + + + + + + + + +``` diff --git a/src/language-html/embed.js b/src/language-html/embed.js index fd9257828..a9660ee4e 100644 --- a/src/language-html/embed.js +++ b/src/language-html/embed.js @@ -57,7 +57,8 @@ async function printEmbeddedAttributeValue(node, htmlTextToDoc, options) { rootNode && (rootNode.type === "ObjectExpression" || rootNode.type === "ArrayExpression" || - (options.parser === "__vue_expression" && + ((options.parser === "__vue_expression" || + options.parser === "__vue_ts_expression") && (rootNode.type === "TemplateLiteral" || rootNode.type === "StringLiteral"))) ) { @@ -105,11 +106,11 @@ async function printEmbeddedAttributeValue(node, htmlTextToDoc, options) { if (options.parser === "vue") { if (node.fullName === "v-for") { - return printVueFor(getValue(), attributeTextToDoc); + return printVueFor(getValue(), attributeTextToDoc, options); } if (isVueSlotAttribute(node) || isVueSfcBindingsAttribute(node, options)) { - return printVueBindings(getValue(), attributeTextToDoc); + return printVueBindings(getValue(), attributeTextToDoc, options); } /** @@ -132,7 +133,9 @@ async function printEmbeddedAttributeValue(node, htmlTextToDoc, options) { if (isKeyMatched(vueEventBindingPatterns)) { const value = getValue(); const parser = isVueEventBindingExpression(value) - ? "__js_expression" + ? options.__should_parse_vue_template_with_ts + ? "__ts_expression" + : "__js_expression" : options.__should_parse_vue_template_with_ts ? "__vue_ts_event_binding" : "__vue_event_binding"; @@ -141,13 +144,21 @@ async function printEmbeddedAttributeValue(node, htmlTextToDoc, options) { if (isKeyMatched(vueExpressionBindingPatterns)) { return printMaybeHug( - await attributeTextToDoc(getValue(), { parser: "__vue_expression" }) + await attributeTextToDoc(getValue(), { + parser: options.__should_parse_vue_template_with_ts + ? "__vue_ts_expression" + : "__vue_expression", + }) ); } if (isKeyMatched(jsExpressionBindingPatterns)) { return printMaybeHug( - await attributeTextToDoc(getValue(), { parser: "__js_expression" }) + await attributeTextToDoc(getValue(), { + parser: options.__should_parse_vue_template_with_ts + ? "__ts_expression" + : "__js_expression", + }) ); } } diff --git a/src/language-html/syntax-vue.js b/src/language-html/syntax-vue.js index 19642bc5d..b95118311 100644 --- a/src/language-html/syntax-vue.js +++ b/src/language-html/syntax-vue.js @@ -1,24 +1,35 @@ import { group } from "../document/builders.js"; +/** + * @typedef {import("../document/builders.js").Doc} Doc + */ /** * v-for="... in ..." * v-for="... of ..." * v-for="(..., ...) in ..." * v-for="(..., ...) of ..." + * + * @param {*} value + * @param {(code: string, opts: *) => Doc} attributeTextToDoc + * @param {*} options + * @returns {Promise} */ -async function printVueFor(value, attributeTextToDoc) { +async function printVueFor(value, attributeTextToDoc, options) { const { left, operator, right } = parseVueFor(value); + const parseWithTs = options.__should_parse_vue_template_with_ts; return [ group( await attributeTextToDoc(`function _(${left}) {}`, { - parser: "babel", + parser: parseWithTs ? "babel-ts" : "babel", __isVueForBindingLeft: true, }) ), " ", operator, " ", - await attributeTextToDoc(right, { parser: "__js_expression" }), + await attributeTextToDoc(right, { + parser: parseWithTs ? "__ts_expression" : "__js_expression", + }), ]; } @@ -68,9 +79,15 @@ function parseVueFor(value) { }; } -function printVueBindings(value, attributeTextToDoc) { +/** + * @param {*} value + * @param {(code: string, opts: *) => Doc} attributeTextToDoc + * @param {*} options + * @returns {Doc} + */ +function printVueBindings(value, attributeTextToDoc, options) { return attributeTextToDoc(`function _(${value}) {}`, { - parser: "babel", + parser: options.__should_parse_vue_template_with_ts ? "babel-ts" : "babel", __isVueBindings: true, }); } diff --git a/src/language-js/parse/babel.js b/src/language-js/parse/babel.js index b26cfb2a5..432843004 100644 --- a/src/language-js/parse/babel.js +++ b/src/language-js/parse/babel.js @@ -252,6 +252,7 @@ export default { "babel-ts": babelTs, /** @internal */ __js_expression: babelExpression, + __ts_expression: babelTSExpression, /** for vue filter */ __vue_expression: babelExpression, /** for vue filter written in TS */ diff --git a/src/language-js/print/binaryish.js b/src/language-js/print/binaryish.js index 047eb32d0..7091e9711 100644 --- a/src/language-js/print/binaryish.js +++ b/src/language-js/print/binaryish.js @@ -339,7 +339,8 @@ const isBitwiseOrExpression = (node) => function isVueFilterSequenceExpression(path, options) { return ( - options.parser === "__vue_expression" && + (options.parser === "__vue_expression" || + options.parser === "__vue_ts_expression") && isBitwiseOrExpression(path.node) && !path.hasAncestor( (node) => !isBitwiseOrExpression(node) && node.type !== "JsExpressionRoot" diff --git a/src/main/comments/attach.js b/src/main/comments/attach.js index 07208d84b..7f4585cb9 100644 --- a/src/main/comments/attach.js +++ b/src/main/comments/attach.js @@ -180,6 +180,7 @@ function attachComments(ast, options) { options.parser === "json" || options.parser === "json5" || options.parser === "__js_expression" || + options.parser === "__ts_expression" || options.parser === "__vue_expression" || options.parser === "__vue_ts_expression" ) { diff --git a/src/plugins/babel.js b/src/plugins/babel.js index 752da8a6d..10d5b429e 100644 --- a/src/plugins/babel.js +++ b/src/plugins/babel.js @@ -9,6 +9,7 @@ export const parsers = createParsers([ "babel-flow", "babel-ts", "__js_expression", + "__ts_expression", "__vue_expression", "__vue_ts_expression", "__vue_event_binding", diff --git a/tests/format/vue/ts-event-binding/__snapshots__/jsfmt.spec.js.snap b/tests/format/vue/ts-event-binding/__snapshots__/jsfmt.spec.js.snap index d5c639848..54d06999f 100644 --- a/tests/format/vue/ts-event-binding/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/vue/ts-event-binding/__snapshots__/jsfmt.spec.js.snap @@ -41,3 +41,39 @@ function log(...args) { ================================================================================ `; + +exports[`function-expression.vue format 1`] = ` +====================================options===================================== +parsers: ["vue"] +printWidth: 80 + | printWidth +=====================================input====================================== + + + + +=====================================output===================================== + + + + +================================================================================ +`; diff --git a/tests/format/vue/ts-event-binding/function-expression.vue b/tests/format/vue/ts-event-binding/function-expression.vue new file mode 100644 index 000000000..ffb1d01ff --- /dev/null +++ b/tests/format/vue/ts-event-binding/function-expression.vue @@ -0,0 +1,9 @@ + + + diff --git a/tests/format/vue/ts-expression/__snapshots__/jsfmt.spec.js.snap b/tests/format/vue/ts-expression/__snapshots__/jsfmt.spec.js.snap index 004db1fc1..317e9e56b 100644 --- a/tests/format/vue/ts-expression/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/vue/ts-expression/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,41 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`attribute-expr.vue format 1`] = ` +====================================options===================================== +parsers: ["vue"] +printWidth: 80 + | printWidth +=====================================input====================================== + + + + +=====================================output===================================== + + + + +================================================================================ +`; + exports[`basic.vue format 1`] = ` ====================================options===================================== parsers: ["vue"] @@ -8,6 +44,7 @@ printWidth: 80 =====================================input====================================== @@ -15,6 +52,7 @@ printWidth: 80 =====================================output===================================== @@ -22,6 +60,123 @@ printWidth: 80 ================================================================================ `; +exports[`comment.vue format 1`] = ` +====================================options===================================== +parsers: ["vue"] +printWidth: 80 + | printWidth +=====================================input====================================== + + + + +=====================================output===================================== + + + + +================================================================================ +`; + +exports[`filter.vue format 1`] = ` +====================================options===================================== +parsers: ["vue"] +printWidth: 80 + | printWidth +=====================================input====================================== + + + + + +=====================================output===================================== + + + + + +================================================================================ +`; + exports[`not-working-with-non-ts-script.vue format 1`] = ` ====================================options===================================== parsers: ["vue"] @@ -30,6 +185,7 @@ printWidth: 80 =====================================input====================================== @@ -37,6 +193,7 @@ printWidth: 80 =====================================output===================================== diff --git a/tests/format/vue/ts-expression/attribute-expr.vue b/tests/format/vue/ts-expression/attribute-expr.vue new file mode 100644 index 000000000..25747b3ac --- /dev/null +++ b/tests/format/vue/ts-expression/attribute-expr.vue @@ -0,0 +1,7 @@ + + + diff --git a/tests/format/vue/ts-expression/basic.vue b/tests/format/vue/ts-expression/basic.vue index 397f810cc..8fa91cce5 100644 --- a/tests/format/vue/ts-expression/basic.vue +++ b/tests/format/vue/ts-expression/basic.vue @@ -1,5 +1,6 @@ diff --git a/tests/format/vue/ts-expression/comment.vue b/tests/format/vue/ts-expression/comment.vue new file mode 100644 index 000000000..6dd32e52a --- /dev/null +++ b/tests/format/vue/ts-expression/comment.vue @@ -0,0 +1,11 @@ + + + diff --git a/tests/format/vue/ts-expression/filter.vue b/tests/format/vue/ts-expression/filter.vue new file mode 100644 index 000000000..db99030e1 --- /dev/null +++ b/tests/format/vue/ts-expression/filter.vue @@ -0,0 +1,19 @@ + + + + diff --git a/tests/format/vue/ts-expression/not-working-with-non-ts-script.vue b/tests/format/vue/ts-expression/not-working-with-non-ts-script.vue index 06afe1962..6b9163183 100644 --- a/tests/format/vue/ts-expression/not-working-with-non-ts-script.vue +++ b/tests/format/vue/ts-expression/not-working-with-non-ts-script.vue @@ -1,5 +1,6 @@ diff --git a/tests/format/vue/v-for/__snapshots__/jsfmt.spec.js.snap b/tests/format/vue/v-for/__snapshots__/jsfmt.spec.js.snap index 41fbb87e9..3afb13bbb 100644 --- a/tests/format/vue/v-for/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/vue/v-for/__snapshots__/jsfmt.spec.js.snap @@ -19,3 +19,57 @@ printWidth: 80 ================================================================================ `; + +exports[`ts.vue format 1`] = ` +====================================options===================================== +parsers: ["vue"] +printWidth: 80 + | printWidth +=====================================input====================================== + + + +=====================================output===================================== + + + +================================================================================ +`; diff --git a/tests/format/vue/v-for/ts.vue b/tests/format/vue/v-for/ts.vue new file mode 100644 index 000000000..f2d695c6a --- /dev/null +++ b/tests/format/vue/v-for/ts.vue @@ -0,0 +1,10 @@ + + diff --git a/tests/format/vue/vue/__snapshots__/jsfmt.spec.js.snap b/tests/format/vue/vue/__snapshots__/jsfmt.spec.js.snap index 657afbaac..eb24b739a 100644 --- a/tests/format/vue/vue/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/vue/vue/__snapshots__/jsfmt.spec.js.snap @@ -1752,63 +1752,35 @@ semi: false =====================================input====================================== - - - - - =====================================output===================================== - - - - - =====================================output===================================== @@ -2071,9 +2131,9 @@ trailingComma: "es5" class="not-allowed" v-if=" value | - thisIsARealSuperLongFilterPipe('arg1', arg2) | - anotherPipeLongJustForFun | - pipeTheThird + thisIsARealSuperLongBitwiseOr('arg1', arg2) | + anotherBitwiseOrLongJustForFun | + bitwiseOrTheThird " > @@ -2093,7 +2153,7 @@ trailingComma: "none"
{{value | thisIsARealSuperLongFilterPipe("arg1", arg2) | anotherPipeLongJustForFun | pipeTheThird}}
-
+
=====================================output===================================== @@ -2129,9 +2189,9 @@ trailingComma: "none" class="not-allowed" v-if=" value | - thisIsARealSuperLongFilterPipe('arg1', arg2) | - anotherPipeLongJustForFun | - pipeTheThird + thisIsARealSuperLongBitwiseOr('arg1', arg2) | + anotherBitwiseOrLongJustForFun | + bitwiseOrTheThird " > @@ -3480,6 +3540,123 @@ trailingComma: "none" ================================================================================ `; +exports[`slot-ts.vue - {"semi":false} format 1`] = ` +====================================options===================================== +parsers: ["vue"] +printWidth: 80 +semi: false + | printWidth +=====================================input====================================== + + + +=====================================output===================================== + + + +================================================================================ +`; + +exports[`slot-ts.vue - {"trailingComma":"es5"} format 1`] = ` +====================================options===================================== +parsers: ["vue"] +printWidth: 80 +trailingComma: "es5" + | printWidth +=====================================input====================================== + + + +=====================================output===================================== + + + +================================================================================ +`; + +exports[`slot-ts.vue - {"trailingComma":"none"} format 1`] = ` +====================================options===================================== +parsers: ["vue"] +printWidth: 80 +trailingComma: "none" + | printWidth +=====================================input====================================== + + + +=====================================output===================================== + + + +================================================================================ +`; + exports[`style.vue - {"semi":false} format 1`] = ` ====================================options===================================== parsers: ["vue"] diff --git a/tests/format/vue/vue/expression-binding-ts.vue b/tests/format/vue/vue/expression-binding-ts.vue new file mode 100644 index 000000000..66bc2f045 --- /dev/null +++ b/tests/format/vue/vue/expression-binding-ts.vue @@ -0,0 +1,13 @@ + + + diff --git a/tests/format/vue/vue/expression-binding.vue b/tests/format/vue/vue/expression-binding.vue index 30622df13..e55a4caf6 100644 --- a/tests/format/vue/vue/expression-binding.vue +++ b/tests/format/vue/vue/expression-binding.vue @@ -1,20 +1,12 @@ - - - - - diff --git a/tests/format/vue/vue/filter.vue b/tests/format/vue/vue/filter.vue index 3ab5f8ba7..a425be2b9 100644 --- a/tests/format/vue/vue/filter.vue +++ b/tests/format/vue/vue/filter.vue @@ -3,5 +3,5 @@
{{value | thisIsARealSuperLongFilterPipe("arg1", arg2) | anotherPipeLongJustForFun | pipeTheThird}}
-
+
diff --git a/tests/format/vue/vue/slot-ts.vue b/tests/format/vue/vue/slot-ts.vue new file mode 100644 index 000000000..3ff4dc348 --- /dev/null +++ b/tests/format/vue/vue/slot-ts.vue @@ -0,0 +1,13 @@ + + diff --git a/tests/unit/builtin-parsers.js b/tests/unit/builtin-plugins.js similarity index 94% rename from tests/unit/builtin-parsers.js rename to tests/unit/builtin-plugins.js index b0473b5ca..427d32e83 100644 --- a/tests/unit/builtin-parsers.js +++ b/tests/unit/builtin-plugins.js @@ -1,6 +1,6 @@ import loadBuiltinPlugins from "../../src/common/load-builtin-plugins.js"; -// Snapshot to avoid mistakes +// Snapshot to prevent mistakes test("builtin parsers", async () => { const parserNames = (await loadBuiltinPlugins()) .flatMap((plugin) => (plugin.parsers ? Object.keys(plugin.parsers) : [])) @@ -14,6 +14,7 @@ test("builtin parsers", async () => { "__ng_binding", "__ng_directive", "__ng_interpolation", + "__ts_expression", "__vue_event_binding", "__vue_expression", "__vue_ts_event_binding",