From 78f49dac02c94ff945c6d808adb52e86fb5162a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Fri, 2 Oct 2026 21:04:49 +0300 Subject: [PATCH] Fix interpolation boundaries in text (#20117) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: fisker Cheung --- changelog_unreleased/angular/20117.md | 17 +++++++++ changelog_unreleased/vue/20117-2.md | 19 ++++++++++ package.json | 2 +- src/language-html/print-preprocess.js | 37 +++++++++++++++++-- .../angular/interpolation/html/18939.html | 2 + .../html/__snapshots__/format.test.js.snap | 18 +++++++++ .../__snapshots__/format.test.js.snap | 21 +++++++++++ .../vue/interpolation/double-brace-string.vue | 5 +++ yarn.lock | 10 ++--- 9 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 changelog_unreleased/angular/20117.md create mode 100644 changelog_unreleased/vue/20117-2.md create mode 100644 tests/format/angular/interpolation/html/18939.html create mode 100644 tests/format/vue/interpolation/double-brace-string.vue diff --git a/changelog_unreleased/angular/20117.md b/changelog_unreleased/angular/20117.md new file mode 100644 index 000000000..c7e061f04 --- /dev/null +++ b/changelog_unreleased/angular/20117.md @@ -0,0 +1,17 @@ +#### Preserve double-brace placeholders inside string literals (#20117 by @splincode) + + +```html + +{{ "{{num_of_selected_files}} / {{num_of_total_files}} files selected" | translate: {num_of_selected_files: 20, num_of_total_files: 30} }} + + +{{ "{{num_of_selected_files}} / {{ num_of_total_files }} files selected" | +translate: {num_of_selected_files: 20, num_of_total_files: 30} }} + + +{{ + "{{num_of_selected_files}} / {{num_of_total_files}} files selected" + | translate: { num_of_selected_files: 20, num_of_total_files: 30 } +}} +``` diff --git a/changelog_unreleased/vue/20117-2.md b/changelog_unreleased/vue/20117-2.md new file mode 100644 index 000000000..da6ec0a33 --- /dev/null +++ b/changelog_unreleased/vue/20117-2.md @@ -0,0 +1,19 @@ +#### Preserve double-brace placeholders inside string literals (#20117 by @splincode) + + +```html + + + + + + + + +``` diff --git a/package.json b/package.json index b05255b46..6ee6dcb46 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "acorn": "8.18.0", "acorn-jsx": "5.3.2", "angular-estree-parser": "15.5.0", - "angular-html-parser": "10.12.0", + "angular-html-parser": "10.13.0", "camelcase": "9.0.0", "ci-info": "4.4.0", "cjk-regex": "3.5.0", diff --git a/src/language-html/print-preprocess.js b/src/language-html/print-preprocess.js index f7710a2ea..eafbe4c02 100644 --- a/src/language-html/print-preprocess.js +++ b/src/language-html/print-preprocess.js @@ -1,4 +1,4 @@ -import { ParseSourceSpan } from "angular-html-parser"; +import { ParseSourceSpan, TokenType } from "angular-html-parser"; import htmlWhitespace from "../utilities/html-whitespace.js"; import isNonEmptyArray from "../utilities/is-non-empty-array.js"; import { @@ -273,7 +273,6 @@ function extractInterpolation(ast, options) { return; } - const interpolationRegex = /\{\{(.+?)\}\}/s; ast.walk((node) => { if (!canHaveInterpolation(node, options)) { return; @@ -286,7 +285,8 @@ function extractInterpolation(ast, options) { let startSourceSpan = child.sourceSpan.start; let endSourceSpan; - const components = child.value.split(interpolationRegex); + const components = splitInterpolation(child); + for ( let i = 0; i < components.length; @@ -331,6 +331,37 @@ function extractInterpolation(ast, options) { }); } +function splitInterpolation(node) { + const interpolationTokens = node.tokens?.filter( + (token) => + token.type === TokenType.INTERPOLATION && token.parts.length === 3, + ); + + if (!interpolationTokens?.some((token) => token.parts[1].includes("}}"))) { + return node.value.split(/\{\{(.+?)\}\}/s); + } + + const components = []; + const { content } = node.sourceSpan.start.file; + let startOffset = node.sourceSpan.start.offset; + + for (const { parts, sourceSpan } of interpolationTokens) { + components.push( + content.slice(startOffset, sourceSpan.start.offset), + content.slice( + sourceSpan.start.offset + parts[0].length, + sourceSpan.end.offset - parts[2].length, + ), + ); + + startOffset = sourceSpan.end.offset; + } + + components.push(content.slice(startOffset, node.sourceSpan.end.offset)); + + return components; +} + /** * - add `hasLeadingSpaces` field * - add `hasTrailingSpaces` field diff --git a/tests/format/angular/interpolation/html/18939.html b/tests/format/angular/interpolation/html/18939.html new file mode 100644 index 000000000..b1b7b18d4 --- /dev/null +++ b/tests/format/angular/interpolation/html/18939.html @@ -0,0 +1,2 @@ +{{}} +{{ "{{num_of_selected_files}} / {{num_of_total_files}} files selected" | translate: {num_of_selected_files: 20, num_of_total_files: 30} }} diff --git a/tests/format/angular/interpolation/html/__snapshots__/format.test.js.snap b/tests/format/angular/interpolation/html/__snapshots__/format.test.js.snap index 944a8cd6b..b7127688c 100644 --- a/tests/format/angular/interpolation/html/__snapshots__/format.test.js.snap +++ b/tests/format/angular/interpolation/html/__snapshots__/format.test.js.snap @@ -16,6 +16,24 @@ parsers: ["angular"] ================================================================================ `; +exports[`18939.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] + printWidth: 80 (default) | +=====================================input====================================== +{{}} +{{ "{{num_of_selected_files}} / {{num_of_total_files}} files selected" | translate: {num_of_selected_files: 20, num_of_total_files: 30} }} + +=====================================output===================================== +{{}} +{{ + "{{num_of_selected_files}} / {{num_of_total_files}} files selected" + | translate: { num_of_selected_files: 20, num_of_total_files: 30 } +}} + +================================================================================ +`; + exports[`block.html format 1`] = ` ====================================options===================================== parsers: ["angular"] diff --git a/tests/format/vue/interpolation/__snapshots__/format.test.js.snap b/tests/format/vue/interpolation/__snapshots__/format.test.js.snap index fef3ec101..93777ecdb 100644 --- a/tests/format/vue/interpolation/__snapshots__/format.test.js.snap +++ b/tests/format/vue/interpolation/__snapshots__/format.test.js.snap @@ -75,6 +75,27 @@ parsers: ["vue"] ================================================================================ `; +exports[`double-brace-string.vue format 1`] = ` +====================================options===================================== +parsers: ["vue"] + printWidth: 80 (default) | +=====================================input====================================== + + +=====================================output===================================== + + +================================================================================ +`; + exports[`ng-non-bindable.vue format 1`] = ` ====================================options===================================== parsers: ["vue"] diff --git a/tests/format/vue/interpolation/double-brace-string.vue b/tests/format/vue/interpolation/double-brace-string.vue new file mode 100644 index 000000000..1fd2d201b --- /dev/null +++ b/tests/format/vue/interpolation/double-brace-string.vue @@ -0,0 +1,5 @@ + diff --git a/yarn.lock b/yarn.lock index e81e4451e..0403d72cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3372,10 +3372,10 @@ __metadata: languageName: node linkType: hard -"angular-html-parser@npm:10.12.0": - version: 10.12.0 - resolution: "angular-html-parser@npm:10.12.0" - checksum: 10/d71e0faa33c3ad91d07a71f4c5878eb047bb42f506fef3b66d15c5dd94a59eba12599de186adf4518ebe28a2f58b669394943281b91aebefed35987d8e29cbbe +"angular-html-parser@npm:10.13.0": + version: 10.13.0 + resolution: "angular-html-parser@npm:10.13.0" + checksum: 10/e2d37993e888469d061145f2880f523a74b0bbfd772438cd3b17b435b5be7f648ce83d2e83714a8322f755f5b2275fae8ff31b9c9ec37faf6a840d9ea8ce6d86 languageName: node linkType: hard @@ -8288,7 +8288,7 @@ __metadata: acorn: "npm:8.18.0" acorn-jsx: "npm:5.3.2" angular-estree-parser: "npm:15.5.0" - angular-html-parser: "npm:10.12.0" + angular-html-parser: "npm:10.13.0" browserslist: "npm:4.29.0" browserslist-to-esbuild: "npm:2.1.1" buffer: "npm:6.0.3"