From 1d05c4cf2a8b4438d936d80dd78eb6ecb3193774 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 15 Jan 2026 05:53:20 +0800 Subject: [PATCH 01/16] Update workflow branches --- .github/workflows/codeql.yml | 6 ++++-- .github/workflows/dev-test.yml | 4 +++- .github/workflows/eslint-rules.yml | 4 +++- .github/workflows/lint.yml | 4 +++- .github/workflows/prod-test.yml | 4 +++- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d0183b9d8..42a4ee70d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -4,14 +4,16 @@ on: push: branches: - main - - patch-release - next + - v* + - patch-release - "!renovate/**" pull_request: branches: - main - - patch-release - next + - v* + - patch-release - "!renovate/**" permissions: diff --git a/.github/workflows/dev-test.yml b/.github/workflows/dev-test.yml index c072a4bff..ed1810d11 100644 --- a/.github/workflows/dev-test.yml +++ b/.github/workflows/dev-test.yml @@ -4,8 +4,10 @@ on: push: branches: - main - - patch-release - next + - v* + - patch-release + - "!renovate/**" pull_request: permissions: diff --git a/.github/workflows/eslint-rules.yml b/.github/workflows/eslint-rules.yml index 56fef7852..648217826 100644 --- a/.github/workflows/eslint-rules.yml +++ b/.github/workflows/eslint-rules.yml @@ -4,8 +4,10 @@ on: push: branches: - main - - patch-release - next + - v* + - patch-release + - "!renovate/**" paths: - "scripts/tools/eslint-plugin-prettier-internal-rules/**" # This workflow file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 99b124d62..35b986d88 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,8 +4,10 @@ on: push: branches: - main - - patch-release - next + - v* + - patch-release + - "!renovate/**" pull_request: permissions: diff --git a/.github/workflows/prod-test.yml b/.github/workflows/prod-test.yml index 531c9ed87..ff620f8dd 100644 --- a/.github/workflows/prod-test.yml +++ b/.github/workflows/prod-test.yml @@ -4,8 +4,10 @@ on: push: branches: - main - - patch-release - next + - v* + - patch-release + - "!renovate/**" pull_request: permissions: From fa6f533e0f3e36b84887ef34e5945f5c4cf2b4e9 Mon Sep 17 00:00:00 2001 From: Ravindu Liyanapathirana <7352580+ravindUwU@users.noreply.github.com> Date: Tue, 9 Dec 2025 17:17:41 +1100 Subject: [PATCH 02/16] Don't print Angular attribute values that are single template/string literals, on new lines (#18378) Co-authored-by: fisker --- changelog_unreleased/angular/18378.md | 30 ++++++++ src/language-html/embed/utilities.js | 4 +- .../angular/__snapshots__/format.test.js.snap | 24 ++----- .../__snapshots__/format.test.js.snap | 71 +++++++++++++++++++ .../attribute-value-hugging.html | 21 ++++++ .../attribute-value-hugging/format.test.js | 1 + .../vue/vue/__snapshots__/format.test.js.snap | 66 ++++++++++------- tests/format/vue/vue/expression-binding.vue | 12 ++-- 8 files changed, 178 insertions(+), 51 deletions(-) create mode 100644 changelog_unreleased/angular/18378.md create mode 100644 tests/format/angular/attribute-value-hugging/__snapshots__/format.test.js.snap create mode 100644 tests/format/angular/attribute-value-hugging/attribute-value-hugging.html create mode 100644 tests/format/angular/attribute-value-hugging/format.test.js diff --git a/changelog_unreleased/angular/18378.md b/changelog_unreleased/angular/18378.md new file mode 100644 index 000000000..fcd495b9a --- /dev/null +++ b/changelog_unreleased/angular/18378.md @@ -0,0 +1,30 @@ +#### Don't print Angular attribute values that are single template/string literals, on new lines (#18378 by @ravindUwU) + + +```html + + + + + + + + +``` diff --git a/src/language-html/embed/utilities.js b/src/language-html/embed/utilities.js index 9519ed385..4ff7d8518 100644 --- a/src/language-html/embed/utilities.js +++ b/src/language-html/embed/utilities.js @@ -24,7 +24,9 @@ function shouldHugJsExpression(ast, options) { (rootNode.type === "ObjectExpression" || rootNode.type === "ArrayExpression" || ((options.parser === "__vue_expression" || - options.parser === "__vue_ts_expression") && + options.parser === "__vue_ts_expression" || + options.parser === "__ng_binding" || + options.parser === "__ng_directive") && (rootNode.type === "TemplateLiteral" || rootNode.type === "StringLiteral"))) ); diff --git a/tests/format/angular/angular/__snapshots__/format.test.js.snap b/tests/format/angular/angular/__snapshots__/format.test.js.snap index 574d1e5bd..7156e9d70 100644 --- a/tests/format/angular/angular/__snapshots__/format.test.js.snap +++ b/tests/format/angular/angular/__snapshots__/format.test.js.snap @@ -1091,9 +1091,7 @@ printWidth: 1 [target]=" 1 " - [target]=" - 'hello' - " + [target]="'hello'" [target]=" a( 1, @@ -1330,9 +1328,7 @@ printWidth: 1 [target]="{ longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong: true, }" - [error]=" - 'We couldn\\\\\\'t find anything with that name.' - " + [error]="'We couldn\\\\\\'t find anything with that name.'" *ngIf=" form .controls @@ -10074,33 +10070,25 @@ bindon-ngModel " >
Are you diff --git a/tests/format/angular/attribute-value-hugging/__snapshots__/format.test.js.snap b/tests/format/angular/attribute-value-hugging/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..b5889c9f9 --- /dev/null +++ b/tests/format/angular/attribute-value-hugging/__snapshots__/format.test.js.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`attribute-value-hugging.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== + +
+
+ +=====================================output===================================== + +
+
+ +================================================================================ +`; diff --git a/tests/format/angular/attribute-value-hugging/attribute-value-hugging.html b/tests/format/angular/attribute-value-hugging/attribute-value-hugging.html new file mode 100644 index 000000000..ec6edf56b --- /dev/null +++ b/tests/format/angular/attribute-value-hugging/attribute-value-hugging.html @@ -0,0 +1,21 @@ + +
+
diff --git a/tests/format/angular/attribute-value-hugging/format.test.js b/tests/format/angular/attribute-value-hugging/format.test.js new file mode 100644 index 000000000..8cbc48b1a --- /dev/null +++ b/tests/format/angular/attribute-value-hugging/format.test.js @@ -0,0 +1 @@ +runFormatTest(import.meta, ["angular"]); diff --git a/tests/format/vue/vue/__snapshots__/format.test.js.snap b/tests/format/vue/vue/__snapshots__/format.test.js.snap index d60cafd67..cb17b71b5 100644 --- a/tests/format/vue/vue/__snapshots__/format.test.js.snap +++ b/tests/format/vue/vue/__snapshots__/format.test.js.snap @@ -1754,12 +1754,14 @@ semi: false @@ -1768,19 +1770,21 @@ semi: false @@ -1798,12 +1802,14 @@ trailingComma: "es5" @@ -1812,19 +1818,21 @@ trailingComma: "es5" @@ -1842,12 +1850,14 @@ trailingComma: "none" @@ -1856,19 +1866,21 @@ trailingComma: "none" diff --git a/tests/format/vue/vue/expression-binding.vue b/tests/format/vue/vue/expression-binding.vue index e55a4caf6..ae8a9f11c 100644 --- a/tests/format/vue/vue/expression-binding.vue +++ b/tests/format/vue/vue/expression-binding.vue @@ -2,11 +2,13 @@ From f92f80179a7e908ed76ce478ac5c05cb76c68e4a Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 23 Dec 2025 13:27:40 +0800 Subject: [PATCH 03/16] Minor refactor to markdown embed print (#18518) --- src/language-markdown/embed.js | 38 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/language-markdown/embed.js b/src/language-markdown/embed.js index b00b7903b..ab0967330 100644 --- a/src/language-markdown/embed.js +++ b/src/language-markdown/embed.js @@ -6,29 +6,39 @@ import { getFencedCodeBlockValue } from "./utilities.js"; function embed(path, options) { const { node } = path; - if (node.type === "code" && node.lang !== null) { - const parser = inferParser(options, { language: node.lang }); - if (parser) { + switch (node.type) { + case "code": { + const { lang: language } = node; + if (!language) { + return; + } + + const parser = inferParser(options, { language }); + if (!parser) { + return; + } + return async (textToDoc) => { - const styleUnit = options.__inJsTemplate ? "~" : "`"; - const style = styleUnit.repeat( - Math.max(3, getMaxContinuousCount(node.value, styleUnit) + 1), - ); - const newOptions = { parser }; + const textToDocOptions = { parser }; // Override the filepath option. // This is because whether the trailing comma of type parameters // should be printed depends on whether it is `*.ts` or `*.tsx`. // https://github.com/prettier/prettier/issues/15282 - if (node.lang === "ts" || node.lang === "typescript") { - newOptions.filepath = "dummy.ts"; - } else if (node.lang === "tsx") { - newOptions.filepath = "dummy.tsx"; + if (language === "ts" || language === "typescript") { + textToDocOptions.filepath = "dummy.ts"; + } else if (language === "tsx") { + textToDocOptions.filepath = "dummy.tsx"; } const doc = await textToDoc( getFencedCodeBlockValue(node, options.originalText), - newOptions, + textToDocOptions, + ); + + const styleUnit = options.__inJsTemplate ? "~" : "`"; + const style = styleUnit.repeat( + Math.max(3, getMaxContinuousCount(node.value, styleUnit) + 1), ); return markAsRoot([ @@ -42,9 +52,7 @@ function embed(path, options) { ]); }; } - } - switch (node.type) { // MDX case "import": case "export": From 4e401ff7ea54a44ea3fe68e77cbfc4574df0f5a8 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 25 Dec 2025 20:42:58 +0800 Subject: [PATCH 04/16] Support formatting angular syntax in markdown (#18519) --- changelog_unreleased/markdown/18519.md | 37 +++ src/language-markdown/embed.js | 11 +- .../angular/__snapshots__/format.test.js.snap | 294 ++++++++++++++++++ .../code/angular/angular-html-invalid.md | 6 + .../markdown/code/angular/angular-html.md | 8 + .../code/angular/angular-ts-invalid.md | 16 + .../markdown/code/angular/angular-ts.md | 17 + .../markdown/code/angular/format.test.js | 2 + 8 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 changelog_unreleased/markdown/18519.md create mode 100644 tests/format/markdown/code/angular/__snapshots__/format.test.js.snap create mode 100644 tests/format/markdown/code/angular/angular-html-invalid.md create mode 100644 tests/format/markdown/code/angular/angular-html.md create mode 100644 tests/format/markdown/code/angular/angular-ts-invalid.md create mode 100644 tests/format/markdown/code/angular/angular-ts.md create mode 100644 tests/format/markdown/code/angular/format.test.js diff --git a/changelog_unreleased/markdown/18519.md b/changelog_unreleased/markdown/18519.md new file mode 100644 index 000000000..a8fbbfb4e --- /dev/null +++ b/changelog_unreleased/markdown/18519.md @@ -0,0 +1,37 @@ +#### Support formatting Angular syntax in code block (#18519 by @fisker) + + +````md + +```angular-ts +@Component({ + selector: 'app-root', + template: `
Welcome to {{ + Angular}}!
`, +}) +export class App {} +``` + +```angular-html +
Welcome to {{ + Angular}}!
+``` + + +Same as input + + +```angular-ts +@Component({ + selector: "app-root", + template: `
Welcome to {{ Angular }}!
`, +}) +export class App {} +``` + +```angular-html +
Welcome to {{ Angular }}!
+``` +```` diff --git a/src/language-markdown/embed.js b/src/language-markdown/embed.js index ab0967330..19cbe6f19 100644 --- a/src/language-markdown/embed.js +++ b/src/language-markdown/embed.js @@ -13,7 +13,16 @@ function embed(path, options) { return; } - const parser = inferParser(options, { language }); + let parser; + // https://shiki.style/references/engine-js-compat#supported-languages + if (language === "angular-ts") { + parser = inferParser(options, { language: "typescript" }); + } else if (language === "angular-html") { + parser = "angular"; + } else { + parser = inferParser(options, { language }); + } + if (!parser) { return; } diff --git a/tests/format/markdown/code/angular/__snapshots__/format.test.js.snap b/tests/format/markdown/code/angular/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..229c7cae0 --- /dev/null +++ b/tests/format/markdown/code/angular/__snapshots__/format.test.js.snap @@ -0,0 +1,294 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`angular-html.md - {"embeddedLanguageFormatting":"off"} format 1`] = ` +====================================options===================================== +embeddedLanguageFormatting: "off" +parsers: ["markdown"] +printWidth: 80 + | printWidth +=====================================input====================================== +\`\`\`angular-html +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } +\`\`\` + +=====================================output===================================== +\`\`\`angular-html +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } +\`\`\` + +================================================================================ +`; + +exports[`angular-html.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +printWidth: 80 + | printWidth +=====================================input====================================== +\`\`\`angular-html +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } +\`\`\` + +=====================================output===================================== +\`\`\`angular-html +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ } +
+} +\`\`\` + +================================================================================ +`; + +exports[`angular-html-invalid.md - {"embeddedLanguageFormatting":"off"} format 1`] = ` +====================================options===================================== +embeddedLanguageFormatting: "off" +parsers: ["markdown"] +printWidth: 80 + | printWidth +=====================================input====================================== +\`\`\`angular-html + @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } +\`\`\` + +=====================================output===================================== +\`\`\`angular-html + @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ } + } +\`\`\` + +================================================================================ +`; + +exports[`angular-html-invalid.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +printWidth: 80 + | printWidth +=====================================input====================================== +\`\`\`angular-html + @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ } + } +\`\`\` + +=====================================output===================================== +\`\`\`angular-html + @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ } + } +\`\`\` + +================================================================================ +`; + +exports[`angular-ts.md - {"embeddedLanguageFormatting":"off"} format 1`] = ` +====================================options===================================== +embeddedLanguageFormatting: "off" +parsers: ["markdown"] +printWidth: 80 + | printWidth +=====================================input====================================== +\`\`\`angular-ts +type A ={ + Ugly:string +} + +@Component({template: \` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + \` +}) +export class Order { +} +\`\`\` + +=====================================output===================================== +\`\`\`angular-ts +type A ={ + Ugly:string +} + +@Component({template: \` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + \` +}) +export class Order { +} +\`\`\` + +================================================================================ +`; + +exports[`angular-ts.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +printWidth: 80 + | printWidth +=====================================input====================================== +\`\`\`angular-ts +type A ={ + Ugly:string +} + +@Component({template: \` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + \` +}) +export class Order { +} +\`\`\` + +=====================================output===================================== +\`\`\`angular-ts +type A = { + Ugly: string; +}; + +@Component({ + template: \` + @if (orderForm.couponCode().disabled()) { +
+ @for ( + reason of orderForm.couponCode().disabledReasons(); + track reason + ) { +

{{ reason.message }}

+ } +
+ } + \`, +}) +export class Order {} +\`\`\` + +================================================================================ +`; + +exports[`angular-ts-invalid.md - {"embeddedLanguageFormatting":"off"} format 1`] = ` +====================================options===================================== +embeddedLanguageFormatting: "off" +parsers: ["markdown"] +printWidth: 80 + | printWidth +=====================================input====================================== +\`\`\`angular-ts + Ugly:string +} + +@Component({template: \` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + \` +}) +export class Order { +} +\`\`\` + +=====================================output===================================== +\`\`\`angular-ts + Ugly:string +} + +@Component({template: \` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + \` +}) +export class Order { +} +\`\`\` + +================================================================================ +`; + +exports[`angular-ts-invalid.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +printWidth: 80 + | printWidth +=====================================input====================================== +\`\`\`angular-ts + Ugly:string +} + +@Component({template: \` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + \` +}) +export class Order { +} +\`\`\` + +=====================================output===================================== +\`\`\`angular-ts + Ugly:string +} + +@Component({template: \` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + \` +}) +export class Order { +} +\`\`\` + +================================================================================ +`; diff --git a/tests/format/markdown/code/angular/angular-html-invalid.md b/tests/format/markdown/code/angular/angular-html-invalid.md new file mode 100644 index 000000000..422a1a5a8 --- /dev/null +++ b/tests/format/markdown/code/angular/angular-html-invalid.md @@ -0,0 +1,6 @@ +```angular-html + @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ } + } +``` diff --git a/tests/format/markdown/code/angular/angular-html.md b/tests/format/markdown/code/angular/angular-html.md new file mode 100644 index 000000000..bbaec0813 --- /dev/null +++ b/tests/format/markdown/code/angular/angular-html.md @@ -0,0 +1,8 @@ +```angular-html +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } +``` diff --git a/tests/format/markdown/code/angular/angular-ts-invalid.md b/tests/format/markdown/code/angular/angular-ts-invalid.md new file mode 100644 index 000000000..514e328d4 --- /dev/null +++ b/tests/format/markdown/code/angular/angular-ts-invalid.md @@ -0,0 +1,16 @@ +```angular-ts + Ugly:string +} + +@Component({template: ` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + ` +}) +export class Order { +} +``` diff --git a/tests/format/markdown/code/angular/angular-ts.md b/tests/format/markdown/code/angular/angular-ts.md new file mode 100644 index 000000000..420ba08c9 --- /dev/null +++ b/tests/format/markdown/code/angular/angular-ts.md @@ -0,0 +1,17 @@ +```angular-ts +type A ={ + Ugly:string +} + +@Component({template: ` +@if (orderForm.couponCode().disabled()) { +
+ @for (reason of orderForm.couponCode().disabledReasons(); track reason) { +

{{ reason.message }}

+ }
+ } + ` +}) +export class Order { +} +``` diff --git a/tests/format/markdown/code/angular/format.test.js b/tests/format/markdown/code/angular/format.test.js new file mode 100644 index 000000000..cbca7e1b9 --- /dev/null +++ b/tests/format/markdown/code/angular/format.test.js @@ -0,0 +1,2 @@ +runFormatTest(import.meta, ["markdown"]); +runFormatTest(import.meta, ["markdown"], { embeddedLanguageFormatting: "off" }); From e32e1218d6b61c1d6b973dd35a2e2f57ef67fdaa Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Wed, 14 Jan 2026 05:13:21 +0800 Subject: [PATCH 05/16] Angular 21.1: Support spread element in array and object literals (#18596) --- changelog_unreleased/angular/18378.md | 2 +- changelog_unreleased/angular/18596.md | 16 +++++++++++ package.json | 4 +-- .../__snapshots__/format.test.js.snap | 28 +++++++++++++++++++ .../exponentiation-operators.html | 0 .../{v20-syntax => expression}/format.test.js | 0 .../in-operators.html | 0 .../angular/expression/spread-element.html | 7 +++++ .../tagged-template-literal.html | 0 .../template-literal.html | 0 .../void-operators.html | 0 yarn.lock | 22 +++++++-------- 12 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 changelog_unreleased/angular/18596.md rename tests/format/angular/{v20-syntax => expression}/__snapshots__/format.test.js.snap (80%) rename tests/format/angular/{v20-syntax => expression}/exponentiation-operators.html (100%) rename tests/format/angular/{v20-syntax => expression}/format.test.js (100%) rename tests/format/angular/{v20-syntax => expression}/in-operators.html (100%) create mode 100644 tests/format/angular/expression/spread-element.html rename tests/format/angular/{v20-syntax => expression}/tagged-template-literal.html (100%) rename tests/format/angular/{v20-syntax => expression}/template-literal.html (100%) rename tests/format/angular/{v20-syntax => expression}/void-operators.html (100%) diff --git a/changelog_unreleased/angular/18378.md b/changelog_unreleased/angular/18378.md index fcd495b9a..1decf5b69 100644 --- a/changelog_unreleased/angular/18378.md +++ b/changelog_unreleased/angular/18378.md @@ -1,4 +1,4 @@ -#### Don't print Angular attribute values that are single template/string literals, on new lines (#18378 by @ravindUwU) +#### Don't print attribute values that are single template or string literals on new lines (#18378 by @ravindUwU) ```html diff --git a/changelog_unreleased/angular/18596.md b/changelog_unreleased/angular/18596.md new file mode 100644 index 000000000..ebfe8843a --- /dev/null +++ b/changelog_unreleased/angular/18596.md @@ -0,0 +1,16 @@ +#### Support spread element in array and object literals (#18596 by @prettier) + + +```html + + + + + + + + +``` diff --git a/package.json b/package.json index 3c9b3b0af..092a9b626 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "bin" ], "dependencies": { - "@angular/compiler": "21.0.2", + "@angular/compiler": "21.1.0-rc.0", "@babel/code-frame": "8.0.0-beta.3", "@babel/parser": "8.0.0-beta.3", "@babel/types": "8.0.0-beta.3", @@ -55,7 +55,7 @@ "@typescript-eslint/visitor-keys": "8.48.0", "acorn": "8.15.0", "acorn-jsx": "5.3.2", - "angular-estree-parser": "14.1.0", + "angular-estree-parser": "15.2.0", "angular-html-parser": "10.1.1", "camelcase": "9.0.0", "ci-info": "4.3.1", diff --git a/tests/format/angular/v20-syntax/__snapshots__/format.test.js.snap b/tests/format/angular/expression/__snapshots__/format.test.js.snap similarity index 80% rename from tests/format/angular/v20-syntax/__snapshots__/format.test.js.snap rename to tests/format/angular/expression/__snapshots__/format.test.js.snap index 2fbe21142..64d98c6f7 100644 --- a/tests/format/angular/v20-syntax/__snapshots__/format.test.js.snap +++ b/tests/format/angular/expression/__snapshots__/format.test.js.snap @@ -33,6 +33,34 @@ printWidth: 80 ================================================================================ `; +exports[`spread-element.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== +{{ + [... a] +}} + +
+
+ +=====================================output===================================== +{{ [...a] }} + +
+
+ +================================================================================ +`; + exports[`tagged-template-literal.html format 1`] = ` ====================================options===================================== parsers: ["angular"] diff --git a/tests/format/angular/v20-syntax/exponentiation-operators.html b/tests/format/angular/expression/exponentiation-operators.html similarity index 100% rename from tests/format/angular/v20-syntax/exponentiation-operators.html rename to tests/format/angular/expression/exponentiation-operators.html diff --git a/tests/format/angular/v20-syntax/format.test.js b/tests/format/angular/expression/format.test.js similarity index 100% rename from tests/format/angular/v20-syntax/format.test.js rename to tests/format/angular/expression/format.test.js diff --git a/tests/format/angular/v20-syntax/in-operators.html b/tests/format/angular/expression/in-operators.html similarity index 100% rename from tests/format/angular/v20-syntax/in-operators.html rename to tests/format/angular/expression/in-operators.html diff --git a/tests/format/angular/expression/spread-element.html b/tests/format/angular/expression/spread-element.html new file mode 100644 index 000000000..20cf7b962 --- /dev/null +++ b/tests/format/angular/expression/spread-element.html @@ -0,0 +1,7 @@ +{{ + [... a] +}} + +
+
diff --git a/tests/format/angular/v20-syntax/tagged-template-literal.html b/tests/format/angular/expression/tagged-template-literal.html similarity index 100% rename from tests/format/angular/v20-syntax/tagged-template-literal.html rename to tests/format/angular/expression/tagged-template-literal.html diff --git a/tests/format/angular/v20-syntax/template-literal.html b/tests/format/angular/expression/template-literal.html similarity index 100% rename from tests/format/angular/v20-syntax/template-literal.html rename to tests/format/angular/expression/template-literal.html diff --git a/tests/format/angular/v20-syntax/void-operators.html b/tests/format/angular/expression/void-operators.html similarity index 100% rename from tests/format/angular/v20-syntax/void-operators.html rename to tests/format/angular/expression/void-operators.html diff --git a/yarn.lock b/yarn.lock index 03b3b8664..d95915db5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,12 +5,12 @@ __metadata: version: 8 cacheKey: 10 -"@angular/compiler@npm:21.0.2": - version: 21.0.2 - resolution: "@angular/compiler@npm:21.0.2" +"@angular/compiler@npm:21.1.0-rc.0": + version: 21.1.0-rc.0 + resolution: "@angular/compiler@npm:21.1.0-rc.0" dependencies: tslib: "npm:^2.3.0" - checksum: 10/a4ff71f113578beb7fe2cd1ae0a200c81ac46c4a2cb54077ca1143ab828c1536707fcd39b1620052945296a5df1c53da4b27501592c34f02eb5f8a844dd96491 + checksum: 10/a4721142e932fe62b27a5e383b540ccb83cd2ca456e8ff3bdd62be1022f46b46d224213108b1186978de5f0d6e6916e7c074fb4270b16818f66b155f53531ac5 languageName: node linkType: hard @@ -2852,12 +2852,12 @@ __metadata: languageName: node linkType: hard -"angular-estree-parser@npm:14.1.0": - version: 14.1.0 - resolution: "angular-estree-parser@npm:14.1.0" +"angular-estree-parser@npm:15.2.0": + version: 15.2.0 + resolution: "angular-estree-parser@npm:15.2.0" peerDependencies: - "@angular/compiler": ^21.0.0 - checksum: 10/b9517dbdb9461d22ed0de5d3a19c82a520b7f45c2db4bc46990f5e5ad3fa948521e814edd88708bffeea9dca83225de4ff95c8b2e10cd7d432315b229024e39d + "@angular/compiler": ">=21.0.7 || >= 21.1.0-0" + checksum: 10/55aa6d98da1685c3c423d8c0db1766967ca60b7a888005e78f96484883edea46e27210b20c7cb1bb1f578f2fb4cf2835ced71b5acf13dc09c5a6d4bf4c75fa88 languageName: node linkType: hard @@ -7496,7 +7496,7 @@ __metadata: version: 0.0.0-use.local resolution: "prettier@workspace:." dependencies: - "@angular/compiler": "npm:21.0.2" + "@angular/compiler": "npm:21.1.0-rc.0" "@babel/code-frame": "npm:8.0.0-beta.3" "@babel/generator": "npm:8.0.0-beta.3" "@babel/parser": "npm:8.0.0-beta.3" @@ -7517,7 +7517,7 @@ __metadata: "@typescript-eslint/visitor-keys": "npm:8.48.0" acorn: "npm:8.15.0" acorn-jsx: "npm:5.3.2" - angular-estree-parser: "npm:14.1.0" + angular-estree-parser: "npm:15.2.0" angular-html-parser: "npm:10.1.1" base64-arraybuffer-es6: "npm:3.1.0" browserslist: "npm:4.28.0" From ed739f0d66f6905e70c9de9a3dfd0f0de2bf051d Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 15 Jan 2026 05:14:58 +0800 Subject: [PATCH 06/16] Angular 21.1: Support consecutive switch cases (#18593) --- changelog_unreleased/angular/18593.md | 25 ++++ package.json | 2 +- .../print/angular-control-flow-block.js | 35 ++++-- .../__snapshots__/format.test.js.snap | 114 ++++++++++++++++++ .../control-flow/multiple-switch-cases.html | 43 +++++++ yarn.lock | 10 +- 6 files changed, 213 insertions(+), 16 deletions(-) create mode 100644 changelog_unreleased/angular/18593.md create mode 100644 tests/format/angular/control-flow/multiple-switch-cases.html diff --git a/changelog_unreleased/angular/18593.md b/changelog_unreleased/angular/18593.md new file mode 100644 index 000000000..7d1ae4039 --- /dev/null +++ b/changelog_unreleased/angular/18593.md @@ -0,0 +1,25 @@ +#### Add support for multiple switch cases (#18593 by @fisker) + +This feature added in Angular v21.1. + + +```html + +@switch (foo) { + @case (0) + @case (1) { +
case 0 or 1
+ } +} + + +SyntaxError: Incomplete block "case". If you meant to write the @ character, you should use the "@" HTML entity instead. (2:3) + + +@switch (a) { + @case (0) + @case (1) { +
case 0 or 1
+ } +} +``` diff --git a/package.json b/package.json index 092a9b626..a61dbfbd1 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "acorn": "8.15.0", "acorn-jsx": "5.3.2", "angular-estree-parser": "15.2.0", - "angular-html-parser": "10.1.1", + "angular-html-parser": "10.3.0", "camelcase": "9.0.0", "ci-info": "4.3.1", "cjk-regex": "3.4.0", diff --git a/src/language-html/print/angular-control-flow-block.js b/src/language-html/print/angular-control-flow-block.js index e1d0919e0..5f9690403 100644 --- a/src/language-html/print/angular-control-flow-block.js +++ b/src/language-html/print/angular-control-flow-block.js @@ -24,18 +24,20 @@ function printAngularControlFlowBlock(path, options, print) { docs.push(" (", group(print("parameters")), ")"); } - docs.push(" {"); + if (!isSwitchFallthroughCase(node)) { + docs.push(" {"); - const shouldPrintCloseBracket = shouldCloseBlock(node); - if (node.children.length > 0) { - node.firstChild.hasLeadingSpaces = true; - node.lastChild.hasTrailingSpaces = true; - docs.push(indent([hardline, printChildren(path, options, print)])); - if (shouldPrintCloseBracket) { - docs.push(hardline, "}"); + const shouldPrintCloseBracket = shouldCloseBlock(node); + if (node.children.length > 0) { + node.firstChild.hasLeadingSpaces = true; + node.lastChild.hasTrailingSpaces = true; + docs.push(indent([hardline, printChildren(path, options, print)])); + if (shouldPrintCloseBracket) { + docs.push(hardline, "}"); + } + } else if (shouldPrintCloseBracket) { + docs.push("}"); } - } else if (shouldPrintCloseBracket) { - docs.push("}"); } return group(docs, { shouldBreak: true }); @@ -48,6 +50,19 @@ function shouldCloseBlock(node) { ); } +const isSwitchCaseBlock = (node) => + node?.kind === "angularControlFlowBlock" && + (node.name === "case" || node.name === "default"); +// https://github.com/angular/angular/commit/0ad3adc7c6d4094f1e3432a3f2e3bdc9862cb4fa#diff-77a6f285c6ea13d6644ef635e7495e71134d1141af2650cb9ae5631ff1f38bf2R263 +// https://github.com/prettier/prettier/issues/18563#issuecomment-3728354491 +function isSwitchFallthroughCase(node) { + return ( + isSwitchCaseBlock(node) && + node.endSourceSpan && + node.endSourceSpan.start.offset === node.endSourceSpan.end.offset + ); +} + function isPreviousBlockUnClosed(path) { const { previous } = path; return ( diff --git a/tests/format/angular/control-flow/__snapshots__/format.test.js.snap b/tests/format/angular/control-flow/__snapshots__/format.test.js.snap index 8e14f252c..57d672535 100644 --- a/tests/format/angular/control-flow/__snapshots__/format.test.js.snap +++ b/tests/format/angular/control-flow/__snapshots__/format.test.js.snap @@ -526,6 +526,120 @@ printWidth: 80 ================================================================================ `; +exports[`multiple-switch-cases.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== + + +@switch (expr) { + @case ( 'one') + @case ( 'two') { + {{ oneOrTwo()}} + } + @case ( 'three') { + {{ three()}} + } +} + + +@switch (expr) { + @case (1) + @case (2) { + {{oneOrTwo() }} + } + @case (3 ) + @default { + {{default() }} + } +} + +
+ {{message}} + @switch (value()) { + @case ( 0) @case(1 ) { + case 01 + } + @case (2) { + case 2 + } + @default { + default + } + } +
+ +@switch(a){@default @case(2){
default before case
}} +@switch(a){@case(1) @case(2){} @case(3) {
3
}} + +@switch(a){@case(1) @case(2) {}} + +=====================================output===================================== + + +@switch (expr) { + @case ("one") + @case ("two") { + {{ oneOrTwo() }} + } + @case ("three") { + {{ three() }} + } +} + +@switch (expr) { + @case (1) + @case (2) { + {{ oneOrTwo() }} + } + @case (3) + @default { + {{ default() }} + } +} + +
+ {{ message }} + @switch (value()) { + @case (0) + @case (1) { + case 01 + } + @case (2) { + case 2 + } + @default { + default + } + } +
+ +@switch (a) { + @default + @case (2) { +
default before case
+ } +} +@switch (a) { + @case (1) + @case (2) {} + @case (3) { +
3
+ } +} + +@switch (a) { + @case (1) + @case (2) { + + } +} + +================================================================================ +`; + exports[`switch.html format 1`] = ` ====================================options===================================== parsers: ["angular"] diff --git a/tests/format/angular/control-flow/multiple-switch-cases.html b/tests/format/angular/control-flow/multiple-switch-cases.html new file mode 100644 index 000000000..a56e8795d --- /dev/null +++ b/tests/format/angular/control-flow/multiple-switch-cases.html @@ -0,0 +1,43 @@ + + +@switch (expr) { + @case ( 'one') + @case ( 'two') { + {{ oneOrTwo()}} + } + @case ( 'three') { + {{ three()}} + } +} + + +@switch (expr) { + @case (1) + @case (2) { + {{oneOrTwo() }} + } + @case (3 ) + @default { + {{default() }} + } +} + +
+ {{message}} + @switch (value()) { + @case ( 0) @case(1 ) { + case 01 + } + @case (2) { + case 2 + } + @default { + default + } + } +
+ +@switch(a){@default @case(2){
default before case
}} +@switch(a){@case(1) @case(2){} @case(3) {
3
}} + +@switch(a){@case(1) @case(2) {}} diff --git a/yarn.lock b/yarn.lock index d95915db5..173adef45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2861,10 +2861,10 @@ __metadata: languageName: node linkType: hard -"angular-html-parser@npm:10.1.1": - version: 10.1.1 - resolution: "angular-html-parser@npm:10.1.1" - checksum: 10/ad2995b43fb5c6465e7d1de5f6909f79e08486724997bb3417934c7bc0541e5604f563147a68e18b71eebba57dc61e184cf5d47451ed8b19a4b34d79cecb5f60 +"angular-html-parser@npm:10.3.0": + version: 10.3.0 + resolution: "angular-html-parser@npm:10.3.0" + checksum: 10/97078879db8f53fc9645d2cd8c748c9eb3770790ec96f249bd96e0ab9293f65506b7facd201c17f2f18884e82550f3c362972e7b3a5448b0e1fb2a6938e9ef4d languageName: node linkType: hard @@ -7518,7 +7518,7 @@ __metadata: acorn: "npm:8.15.0" acorn-jsx: "npm:5.3.2" angular-estree-parser: "npm:15.2.0" - angular-html-parser: "npm:10.1.1" + angular-html-parser: "npm:10.3.0" base64-arraybuffer-es6: "npm:3.1.0" browserslist: "npm:4.28.0" browserslist-to-esbuild: "npm:2.1.1" From 988efdb3ea26ed4473fbb6fd8f9f142d8ae17184 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 05:27:44 +0800 Subject: [PATCH 07/16] Update Angular Estree parser (#18636) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: fisker --- changelog_unreleased/angular/18596.md | 7 ++++--- package.json | 4 ++-- .../__snapshots__/format.test.js.snap | 5 +++++ .../angular/expression/spread-element.html | 3 +++ yarn.lock | 20 +++++++++---------- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/changelog_unreleased/angular/18596.md b/changelog_unreleased/angular/18596.md index ebfe8843a..ced8302c3 100644 --- a/changelog_unreleased/angular/18596.md +++ b/changelog_unreleased/angular/18596.md @@ -1,4 +1,4 @@ -#### Support spread element in array and object literals (#18596 by @prettier) +#### Support spread element in array and object literals (#18596, #18636 by @prettier) ```html @@ -6,11 +6,12 @@ - + - + ``` diff --git a/package.json b/package.json index a61dbfbd1..90fd49064 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "bin" ], "dependencies": { - "@angular/compiler": "21.1.0-rc.0", + "@angular/compiler": "21.1.0", "@babel/code-frame": "8.0.0-beta.3", "@babel/parser": "8.0.0-beta.3", "@babel/types": "8.0.0-beta.3", @@ -55,7 +55,7 @@ "@typescript-eslint/visitor-keys": "8.48.0", "acorn": "8.15.0", "acorn-jsx": "5.3.2", - "angular-estree-parser": "15.2.0", + "angular-estree-parser": "15.3.0", "angular-html-parser": "10.3.0", "camelcase": "9.0.0", "ci-info": "4.3.1", diff --git a/tests/format/angular/expression/__snapshots__/format.test.js.snap b/tests/format/angular/expression/__snapshots__/format.test.js.snap index 64d98c6f7..65b9a0f52 100644 --- a/tests/format/angular/expression/__snapshots__/format.test.js.snap +++ b/tests/format/angular/expression/__snapshots__/format.test.js.snap @@ -47,6 +47,9 @@ printWidth: 80
+{{ foo(a, +...b)}} + =====================================output===================================== {{ [...a] }} @@ -58,6 +61,8 @@ foo:1, ...bar }"> }" > +{{ foo(a, ...b) }} + ================================================================================ `; diff --git a/tests/format/angular/expression/spread-element.html b/tests/format/angular/expression/spread-element.html index 20cf7b962..b60dc4e95 100644 --- a/tests/format/angular/expression/spread-element.html +++ b/tests/format/angular/expression/spread-element.html @@ -5,3 +5,6 @@
+ +{{ foo(a, +...b)}} diff --git a/yarn.lock b/yarn.lock index 173adef45..56a8c7ae9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,12 +5,12 @@ __metadata: version: 8 cacheKey: 10 -"@angular/compiler@npm:21.1.0-rc.0": - version: 21.1.0-rc.0 - resolution: "@angular/compiler@npm:21.1.0-rc.0" +"@angular/compiler@npm:21.1.0": + version: 21.1.0 + resolution: "@angular/compiler@npm:21.1.0" dependencies: tslib: "npm:^2.3.0" - checksum: 10/a4721142e932fe62b27a5e383b540ccb83cd2ca456e8ff3bdd62be1022f46b46d224213108b1186978de5f0d6e6916e7c074fb4270b16818f66b155f53531ac5 + checksum: 10/1f409c20eb36079719e004f9e683a41d9d9fd975d814132faa28f941a0d491ba392c7cca92061f8dbe540bd922cac905f76ccab48a87c82c764b3b7cdb6b28dd languageName: node linkType: hard @@ -2852,12 +2852,12 @@ __metadata: languageName: node linkType: hard -"angular-estree-parser@npm:15.2.0": - version: 15.2.0 - resolution: "angular-estree-parser@npm:15.2.0" +"angular-estree-parser@npm:15.3.0": + version: 15.3.0 + resolution: "angular-estree-parser@npm:15.3.0" peerDependencies: "@angular/compiler": ">=21.0.7 || >= 21.1.0-0" - checksum: 10/55aa6d98da1685c3c423d8c0db1766967ca60b7a888005e78f96484883edea46e27210b20c7cb1bb1f578f2fb4cf2835ced71b5acf13dc09c5a6d4bf4c75fa88 + checksum: 10/9266d6bf73fac7f2d96e6a7b306bef0a6b6563f4f11a36aa308d4ca2fda2868839c8831c2e95d5ea008636e061c1cdc0668c01fc9f9876eb1b5f6846217fc2ec languageName: node linkType: hard @@ -7496,7 +7496,7 @@ __metadata: version: 0.0.0-use.local resolution: "prettier@workspace:." dependencies: - "@angular/compiler": "npm:21.1.0-rc.0" + "@angular/compiler": "npm:21.1.0" "@babel/code-frame": "npm:8.0.0-beta.3" "@babel/generator": "npm:8.0.0-beta.3" "@babel/parser": "npm:8.0.0-beta.3" @@ -7517,7 +7517,7 @@ __metadata: "@typescript-eslint/visitor-keys": "npm:8.48.0" acorn: "npm:8.15.0" acorn-jsx: "npm:5.3.2" - angular-estree-parser: "npm:15.2.0" + angular-estree-parser: "npm:15.3.0" angular-html-parser: "npm:10.3.0" base64-arraybuffer-es6: "npm:3.1.0" browserslist: "npm:4.28.0" From 695887cccc924f290a931a6b6595655a0c4dd7b5 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 15 Jan 2026 06:09:05 +0800 Subject: [PATCH 08/16] Update snapshots --- .../__snapshots__/format.test.js.snap | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/format/misc/errors/front-matter/__snapshots__/format.test.js.snap b/tests/format/misc/errors/front-matter/__snapshots__/format.test.js.snap index 142573f75..9b5a82c6e 100644 --- a/tests/format/misc/errors/front-matter/__snapshots__/format.test.js.snap +++ b/tests/format/misc/errors/front-matter/__snapshots__/format.test.js.snap @@ -18,19 +18,39 @@ exports[`snippet: Should not support front matter [__js_expression] format 1`] = Cause: Unexpected parseExpression() input: The input should contain exactly one expression, but the first expression is followed by the unexpected character \`M\`. (2:6)" `; -exports[`snippet: Should not support front matter [__ng_action] format 1`] = `"Unexpected token 'Matter'"`; +exports[`snippet: Should not support front matter [__ng_action] format 1`] = ` +"Parser Error: Unexpected token 'Matter' +Cause: Parser Error: Unexpected token 'Matter' at column 11 in [--- +Front Matter content +---] in test.html@0:0" +`; -exports[`snippet: Should not support front matter [__ng_binding] format 1`] = `"Unexpected token 'Matter'"`; +exports[`snippet: Should not support front matter [__ng_binding] format 1`] = ` +"Parser Error: Unexpected token 'Matter' +Cause: Parser Error: Unexpected token 'Matter' at column 11 in [--- +Front Matter content +---] in test.html@0:0" +`; exports[`snippet: Should not support front matter [__ng_directive] format 1`] = ` -"Unexpected end of expression: --- +"Parser Error: Unexpected end of expression: --- +Front Matter content +--- at the end of the expression [--- +Front Matter content +---] +Cause: Parser Error: Unexpected end of expression: --- Front Matter content --- at the end of the expression [--- Front Matter content ---] in test.html@0:0" `; -exports[`snippet: Should not support front matter [__ng_interpolation] format 1`] = `"Unexpected token 'Matter'"`; +exports[`snippet: Should not support front matter [__ng_interpolation] format 1`] = ` +"Parser Error: Unexpected token 'Matter' +Cause: Parser Error: Unexpected token 'Matter' at column 11 in [--- +Front Matter content +---] in test.html@0:0" +`; exports[`snippet: Should not support front matter [__ts_expression] format 1`] = ` "The input should contain exactly one expression, but the first expression is followed by the unexpected character \`M\`. (2:7) From b04d05b831f1476ac6e24e1211972bfdd475c9b8 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Fri, 5 Dec 2025 21:04:19 +0800 Subject: [PATCH 09/16] Remove lint step from release script (#18415) --- scripts/release/run.js | 5 ----- scripts/release/steps/index.js | 1 - scripts/release/steps/lint-files.js | 5 ----- 3 files changed, 11 deletions(-) delete mode 100644 scripts/release/steps/lint-files.js diff --git a/scripts/release/run.js b/scripts/release/run.js index 29fb43156..9ed91e8d6 100644 --- a/scripts/release/run.js +++ b/scripts/release/run.js @@ -37,11 +37,6 @@ for (let step of [ process: steps.installDependencies, skip: params.dry || params.skipDependenciesInstall || !params.manual, }, - { - name: "Linting files", - process: steps.lintFiles, - skip: params.dry, - }, { name: "Bumping version", process: steps.updateVersion, diff --git a/scripts/release/steps/index.js b/scripts/release/steps/index.js index 5a8368903..888dfe24b 100644 --- a/scripts/release/steps/index.js +++ b/scripts/release/steps/index.js @@ -4,7 +4,6 @@ export { default as chooseVersion } from "./choose-version.js"; export { default as cleanChangelog } from "./clean-changelog.js"; export { default as generateBundles } from "./generate-bundles.js"; export { default as installDependencies } from "./install-dependencies.js"; -export { default as lintFiles } from "./lint-files.js"; export { default as mergeBlogPost } from "./merge-blog-post.js"; export { default as postPublishSteps } from "./post-publish-steps.js"; export { default as publishToNpm } from "./publish-to-npm.js"; diff --git a/scripts/release/steps/lint-files.js b/scripts/release/steps/lint-files.js deleted file mode 100644 index 011649829..000000000 --- a/scripts/release/steps/lint-files.js +++ /dev/null @@ -1,5 +0,0 @@ -import { runYarn } from "../utilities.js"; - -const lintFiles = () => runYarn("lint"); - -export default lintFiles; From 04336012b351529f624eaeb3ac9af52a5b7b7c01 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 15 Jan 2026 07:57:39 +0800 Subject: [PATCH 10/16] Add blog post for v3.8.0 (#18639) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- changelog_unreleased/angular/18593.md | 28 +++-- changelog_unreleased/angular/18596.md | 22 ++-- changelog_unreleased/blog-post-intro.md | 15 +++ website/blog/2026-01-14-3.8.0.md | 154 ++++++++++++++++++++++++ 4 files changed, 201 insertions(+), 18 deletions(-) create mode 100644 changelog_unreleased/blog-post-intro.md create mode 100644 website/blog/2026-01-14-3.8.0.md diff --git a/changelog_unreleased/angular/18593.md b/changelog_unreleased/angular/18593.md index 7d1ae4039..8b6f6f806 100644 --- a/changelog_unreleased/angular/18593.md +++ b/changelog_unreleased/angular/18593.md @@ -1,25 +1,31 @@ -#### Add support for multiple switch cases (#18593 by @fisker) - -This feature added in Angular v21.1. +#### [HIGHLIGHT] Support for consecutive switch cases (#18593 by @fisker) ```html -@switch (foo) { - @case (0) - @case (1) { -
case 0 or 1
+@switch (userRole) { + @case ('admin') + @case ('moderator') { +

Welcome, boss! Full access.

} + @case ('user') {

Standard access

} + @default {

Guest view

} } SyntaxError: Incomplete block "case". If you meant to write the @ character, you should use the "@" HTML entity instead. (2:3) -@switch (a) { - @case (0) - @case (1) { -
case 0 or 1
+@switch (userRole) { + @case ("admin") + @case ("moderator") { +

Welcome, boss! Full access.

+ } + @case ("user") { +

Standard access

+ } + @default { +

Guest view

} } ``` diff --git a/changelog_unreleased/angular/18596.md b/changelog_unreleased/angular/18596.md index ced8302c3..249eda0b2 100644 --- a/changelog_unreleased/angular/18596.md +++ b/changelog_unreleased/angular/18596.md @@ -1,17 +1,25 @@ -#### Support spread element in array and object literals (#18596, #18636 by @prettier) +#### [HIGHLIGHT] Support for spread elements (#18596, #18636 by @prettier) ```html - - + - + ``` diff --git a/changelog_unreleased/blog-post-intro.md b/changelog_unreleased/blog-post-intro.md new file mode 100644 index 000000000..10b26d689 --- /dev/null +++ b/changelog_unreleased/blog-post-intro.md @@ -0,0 +1,15 @@ +--- +authors: "fisker" +title: "Prettier 3.8: Support for Angular v21.1" +--- + +We're excited to announce that Prettier now fully supports the fresh features landing in Angular v21.1 (released today 🎉)! + +This update brings cleaner, more expressive templates with: + +- Consecutive `@case` statements in `@switch` blocks. +- Spread elements (`...`) in array literals, object literals, and function calls inside templates. + +We've also added the ability to format Angular syntax beautifully inside Markdown code blocks. + +If you find Prettier valuable and want to help us keep pace with fast-moving frameworks like Angular, please consider [sponsoring us on OpenCollective](https://opencollective.com/prettier) or supporting the projects we rely on. Thank you for being part of this community — your support means a lot! diff --git a/website/blog/2026-01-14-3.8.0.md b/website/blog/2026-01-14-3.8.0.md new file mode 100644 index 000000000..826caf632 --- /dev/null +++ b/website/blog/2026-01-14-3.8.0.md @@ -0,0 +1,154 @@ +--- +authors: "fisker" +title: "Prettier 3.8: Support for Angular v21.1" +--- + +We're excited to announce that Prettier now fully supports the fresh features landing in Angular v21.1 (released today 🎉)! + +This update brings cleaner, more expressive templates with: + +- Consecutive `@case` statements in `@switch` blocks. +- Spread elements (`...`) in array literals, object literals, and function calls inside templates. + +We've also added the ability to format Angular syntax beautifully inside Markdown code blocks. + +If you find Prettier valuable and want to help us keep pace with fast-moving frameworks like Angular, please consider [sponsoring us on OpenCollective](https://opencollective.com/prettier) or supporting the projects we rely on. Thank you for being part of this community — your support means a lot! + + + +## Highlights + +### Angular + +#### Support for consecutive switch cases ([#18593](https://github.com/prettier/prettier/pull/18593) by [@fisker](https://github.com/fisker)) {#change-18593} + + +```html + +@switch (userRole) { + @case ('admin') + @case ('moderator') { +

Welcome, boss! Full access.

+ } + @case ('user') {

Standard access

} + @default {

Guest view

} +} + + +SyntaxError: Incomplete block "case". If you meant to write the @ character, you should use the "@" HTML entity instead. (2:3) + + +@switch (userRole) { + @case ("admin") + @case ("moderator") { +

Welcome, boss! Full access.

+ } + @case ("user") { +

Standard access

+ } + @default { +

Guest view

+ } +} +``` + +#### Support for spread elements ([#18596](https://github.com/prettier/prettier/pull/18596), [#18636](https://github.com/prettier/prettier/pull/18636) by [@prettier](https://github.com/prettier)) {#change-18596} + + +```html + + + + + + + + +``` + +## Other Changes + +### Angular + +#### Don't print attribute values that are single template or string literals on new lines ([#18378](https://github.com/prettier/prettier/pull/18378) by [@ravindUwU](https://github.com/ravindUwU)) {#change-18378} + + +```html + + + + + + + + +``` + +### Markdown + +#### Support formatting Angular syntax in code block ([#18519](https://github.com/prettier/prettier/pull/18519) by [@fisker](https://github.com/fisker)) {#change-18519} + + +````md + +```angular-ts +@Component({ + selector: 'app-root', + template: `
Welcome to {{ + Angular}}!
`, +}) +export class App {} +``` + +```angular-html +
Welcome to {{ + Angular}}!
+``` + + +Same as input + + +```angular-ts +@Component({ + selector: "app-root", + template: `
Welcome to {{ Angular }}!
`, +}) +export class App {} +``` + +```angular-html +
Welcome to {{ Angular }}!
+``` +```` From 5824b15189303d52892ffbc0812751533666c674 Mon Sep 17 00:00:00 2001 From: fisker Date: Thu, 15 Jan 2026 07:54:41 +0800 Subject: [PATCH 11/16] Release 3.8.0 --- .github/ISSUE_TEMPLATE/formatting.md | 2 +- .github/ISSUE_TEMPLATE/integration.md | 2 +- CHANGELOG.md | 6 ++++ package.json | 2 +- .../versioned_docs/version-stable/browser.md | 36 +++++++++---------- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/formatting.md b/.github/ISSUE_TEMPLATE/formatting.md index 0336a4f06..cdaae2cfc 100644 --- a/.github/ISSUE_TEMPLATE/formatting.md +++ b/.github/ISSUE_TEMPLATE/formatting.md @@ -26,7 +26,7 @@ Don't fill the form below manually! Let a program create a report for you: --> -**Prettier 3.7.4** +**Prettier 3.8.0** [Playground link](https://prettier.io/playground/#.....) ```sh diff --git a/.github/ISSUE_TEMPLATE/integration.md b/.github/ISSUE_TEMPLATE/integration.md index 9e81974b0..043eba44d 100644 --- a/.github/ISSUE_TEMPLATE/integration.md +++ b/.github/ISSUE_TEMPLATE/integration.md @@ -20,7 +20,7 @@ BEFORE SUBMITTING AN ISSUE: **Environments:** -- Prettier Version: 3.7.4 +- Prettier Version: 3.8.0 - Running Prettier via: - Runtime: - Operating System: diff --git a/CHANGELOG.md b/CHANGELOG.md index a261d0180..d19455804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 3.8.0 + +[diff](https://github.com/prettier/prettier/compare/3.7.4...3.8.0) + +🔗 [Release Notes](https://prettier.io/blog/2026/01/14/3.8.0) + # 3.7.4 [diff](https://github.com/prettier/prettier/compare/3.7.3...3.7.4) diff --git a/package.json b/package.json index 90fd49064..b992cc367 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prettier", - "version": "3.8.0-dev", + "version": "3.8.0", "description": "Prettier is an opinionated code formatter", "bin": "./bin/prettier.cjs", "repository": "prettier/prettier", diff --git a/website/versioned_docs/version-stable/browser.md b/website/versioned_docs/version-stable/browser.md index 723421131..c432d8af2 100644 --- a/website/versioned_docs/version-stable/browser.md +++ b/website/versioned_docs/version-stable/browser.md @@ -21,7 +21,7 @@ Required options: - **[`parser`](options.md#parser) (or [`filepath`](options.md#file-path))**: One of these options has to be specified for Prettier to know which parser to use. -- **`plugins`**: Unlike the `format` function from the [Node.js-based API](api.md#prettierformatsource-options), this function doesn’t load plugins automatically. The `plugins` option is required because all the parsers included in the Prettier package come as plugins (for reasons of file size). These plugins are files in [https://unpkg.com/browse/prettier@3.7.4/plugins](https://unpkg.com/browse/prettier@3.7.4/plugins). Note that `estree` plugin should be loaded when printing JavaScript, TypeScript, Flow, or JSON. +- **`plugins`**: Unlike the `format` function from the [Node.js-based API](api.md#prettierformatsource-options), this function doesn’t load plugins automatically. The `plugins` option is required because all the parsers included in the Prettier package come as plugins (for reasons of file size). These plugins are files in [https://unpkg.com/browse/prettier@3.8.0/plugins](https://unpkg.com/browse/prettier@3.8.0/plugins). Note that `estree` plugin should be loaded when printing JavaScript, TypeScript, Flow, or JSON. You need to load the ones that you’re going to use and pass them to `prettier.format` using the `plugins` option. @@ -32,8 +32,8 @@ See below for examples. ### Global ```html - - + +