From bd9e11ab41e17a4f61f363a981c1ec24d2a4167a Mon Sep 17 00:00:00 2001 From: Seiya <20365512+seiyab@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:10:31 +0900 Subject: [PATCH 1/5] Correct text identification in liquid syntax (#19489) Co-authored-by: fisker fix #19488: check corresponding closing token in liquid syntax fix #19488 --- changelog_unreleased/markdown/19489.md | 17 ++ .../micromark/micromark-extension-liquid.js | 63 ++---- .../liquid/__snapshots__/format.test.js.snap | 196 +++++++++++++++++- .../liquid/character-after-closing-tokens.md | 9 + tests/format/markdown/liquid/issue-19488.md | 27 +++ 5 files changed, 265 insertions(+), 47 deletions(-) create mode 100644 changelog_unreleased/markdown/19489.md create mode 100644 tests/format/markdown/liquid/character-after-closing-tokens.md create mode 100644 tests/format/markdown/liquid/issue-19488.md diff --git a/changelog_unreleased/markdown/19489.md b/changelog_unreleased/markdown/19489.md new file mode 100644 index 000000000..51a184821 --- /dev/null +++ b/changelog_unreleased/markdown/19489.md @@ -0,0 +1,17 @@ +#### Fix unexpected removal of characters in liquid syntax (#19489 by @seiyab) + + +```md +// Input + +{{ page.title +}} text + + +{{ page.title + text + + +{{ page.title +}} text +``` diff --git a/src/language-markdown/parse/micromark/micromark-extension-liquid.js b/src/language-markdown/parse/micromark/micromark-extension-liquid.js index 808879086..cac4a27a4 100644 --- a/src/language-markdown/parse/micromark/micromark-extension-liquid.js +++ b/src/language-markdown/parse/micromark/micromark-extension-liquid.js @@ -33,10 +33,10 @@ function liquidFromMarkdown() { /** @type {Handle} */ function exit(token) { - const d = this.resume(); + this.resume(); /** @type {any} */ const node = this.stack.at(-1); - node.value = d; + node.value = this.sliceSerialize(token); this.exit(token); } } @@ -55,6 +55,9 @@ function liquidSyntax() { }; function tokenize(effects, ok, nok) { + /** @type {typeof codes.rightCurlyBrace | typeof codes.percentSign} */ + let closingCode; + return start; /** @type {State} */ @@ -66,6 +69,10 @@ function liquidSyntax() { switch (code) { case codes.percentSign: case codes.leftCurlyBrace: + closingCode = + code === codes.percentSign + ? codes.percentSign + : codes.rightCurlyBrace; effects.consume(code); return inside; default: @@ -77,9 +84,9 @@ function liquidSyntax() { /** @type {State} */ function inside(code) { switch (code) { - case codes.percentSign: - case codes.rightCurlyBrace: - return effects.check({ tokenize: tokenizeClose }, close, data)(code); + case closingCode: + effects.consume(code); + return mayClose; case codes.eof: return nok(code); default: @@ -91,53 +98,21 @@ function liquidSyntax() { effects.enter(types.data); return inside; } - return data(code); + effects.consume(code); + return inside; } } /** @type {State} */ - function data(code) { - effects.consume(code); - return inside; - } - - function tokenizeClose(effects, ok, nok) { - return startClose; - - /** @type {State} */ - function startClose(code) { - switch (code) { - case codes.percentSign: - case codes.rightCurlyBrace: - effects.consume(code); - return endClose; - default: - return nok(code); - } - } - - /** @type {State} */ - function endClose(code) { - if (code !== codes.rightCurlyBrace) { - return nok(code); - } + function mayClose(code) { + if (code === codes.rightCurlyBrace) { effects.consume(code); + effects.exit(types.data); + effects.exit(nodeType); return ok; } - } - /** @type {State} */ - function close(code) { - effects.consume(code); - return closeEnd; - } - - /** @type {State} */ - function closeEnd(code) { - effects.consume(code); - effects.exit(types.data); - effects.exit(nodeType); - return ok; + return inside; } } } diff --git a/tests/format/markdown/liquid/__snapshots__/format.test.js.snap b/tests/format/markdown/liquid/__snapshots__/format.test.js.snap index d79547f8f..f7220d61a 100644 --- a/tests/format/markdown/liquid/__snapshots__/format.test.js.snap +++ b/tests/format/markdown/liquid/__snapshots__/format.test.js.snap @@ -1,5 +1,63 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`character-after-closing-tokens.md - {"proseWrap":"always"} format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +proseWrap: "always" + printWidth: 80 (default) | +=====================================input====================================== +{{ page.title +}} a + +--- + +{% + if user +%} Hello, {{ user }}! +{% endif %} + +=====================================output===================================== +{{ page.title +}} a + +--- + +{% + if user +%} Hello, {{ user }}! {% endif %} + +================================================================================ +`; + +exports[`character-after-closing-tokens.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] + printWidth: 80 (default) | +=====================================input====================================== +{{ page.title +}} a + +--- + +{% + if user +%} Hello, {{ user }}! +{% endif %} + +=====================================output===================================== +{{ page.title +}} a + +--- + +{% + if user +%} Hello, {{ user }}! +{% endif %} + +================================================================================ +`; + exports[`example-1.md - {"proseWrap":"always"} format 1`] = ` ====================================options===================================== parsers: ["markdown"] @@ -216,6 +274,138 @@ parsers: ["markdown"] ================================================================================ `; +exports[`issue-19488.md - {"proseWrap":"always"} format 1`] = ` +====================================options===================================== +parsers: ["markdown"] +proseWrap: "always" + printWidth: 80 (default) | +=====================================input====================================== +{{% blocks/cover +title="Welcome" +%}} + +This input uses Hugo shortcode. Prettier doesn't support this explicitly but it shouldn't break it either. + +{{% /blocks/cover %}} + +--- + +{{% blocks/cover +%}} + +{{% blocks/cover +%%}} + +{{% blocks/cover +}}} + +{{{% blocks/cover +%}} + +{% blocks/cover +%}} + +{{ blocks/cover +%}} + +=====================================output===================================== +{{% blocks/cover +title="Welcome" +%}} + +This input uses Hugo shortcode. Prettier doesn't support this explicitly but it +shouldn't break it either. + +{{% /blocks/cover %}} + +--- + +{{% blocks/cover +%}} + +{{% blocks/cover +%%}} + +{{% blocks/cover +}}} + +{{{% blocks/cover +%}} + +{% blocks/cover +%}} + +{{ blocks/cover +%}} + +================================================================================ +`; + +exports[`issue-19488.md format 1`] = ` +====================================options===================================== +parsers: ["markdown"] + printWidth: 80 (default) | +=====================================input====================================== +{{% blocks/cover +title="Welcome" +%}} + +This input uses Hugo shortcode. Prettier doesn't support this explicitly but it shouldn't break it either. + +{{% /blocks/cover %}} + +--- + +{{% blocks/cover +%}} + +{{% blocks/cover +%%}} + +{{% blocks/cover +}}} + +{{{% blocks/cover +%}} + +{% blocks/cover +%}} + +{{ blocks/cover +%}} + +=====================================output===================================== +{{% blocks/cover +title="Welcome" +%}} + +This input uses Hugo shortcode. Prettier doesn't support this explicitly but it shouldn't break it either. + +{{% /blocks/cover %}} + +--- + +{{% blocks/cover +%}} + +{{% blocks/cover +%%}} + +{{% blocks/cover +}}} + +{{{% blocks/cover +%}} + +{% blocks/cover +%}} + +{{ blocks/cover +%}} + +================================================================================ +`; + exports[`unbalanced-mismatched-braces.md - {"proseWrap":"always"} format 1`] = ` ====================================options===================================== parsers: ["markdown"] @@ -226,8 +416,8 @@ proseWrap: "always" Braces {% doesn't match so these words should not be considered as *liquid node* }} =====================================output===================================== -Braces -{% doesn't match so these words should not be considered as *liquid node* }} +Braces {% doesn't match so these words should not be considered as _liquid node_ +}} ================================================================================ `; @@ -241,7 +431,7 @@ parsers: ["markdown"] Braces {% doesn't match so these words should not be considered as *liquid node* }} =====================================output===================================== -Braces {% doesn't match so these words should not be considered as *liquid node* }} +Braces {% doesn't match so these words should not be considered as _liquid node_ }} ================================================================================ `; diff --git a/tests/format/markdown/liquid/character-after-closing-tokens.md b/tests/format/markdown/liquid/character-after-closing-tokens.md new file mode 100644 index 000000000..b3c601f9b --- /dev/null +++ b/tests/format/markdown/liquid/character-after-closing-tokens.md @@ -0,0 +1,9 @@ +{{ page.title +}} a + +--- + +{% + if user +%} Hello, {{ user }}! +{% endif %} diff --git a/tests/format/markdown/liquid/issue-19488.md b/tests/format/markdown/liquid/issue-19488.md new file mode 100644 index 000000000..71d14d01b --- /dev/null +++ b/tests/format/markdown/liquid/issue-19488.md @@ -0,0 +1,27 @@ +{{% blocks/cover +title="Welcome" +%}} + +This input uses Hugo shortcode. Prettier doesn't support this explicitly but it shouldn't break it either. + +{{% /blocks/cover %}} + +--- + +{{% blocks/cover +%}} + +{{% blocks/cover +%%}} + +{{% blocks/cover +}}} + +{{{% blocks/cover +%}} + +{% blocks/cover +%}} + +{{ blocks/cover +%}} From a74a7b05dee7fbef39a6aeff378c3741e1a8ee15 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 29 Jun 2026 12:22:38 +0100 Subject: [PATCH 2/5] Allow decorators to be used with `declare` on class fields (#19492) Co-authored-by: fisker --- changelog_unreleased/typescript/19492.md | 23 ++++++++++ src/language-js/parse/babel.js | 7 +++ tests/format/typescript/comments/16065-2.ts | 8 ++++ .../__snapshots__/format.test.js.snap | 16 +++++++ .../__snapshots__/format.test.js.snap | 43 +++++++++++++++++++ .../decorators/decorators-comments.ts | 6 +++ tests/format/typescript/decorators/ember.ts | 10 +++++ 7 files changed, 113 insertions(+) create mode 100644 changelog_unreleased/typescript/19492.md create mode 100644 tests/format/typescript/decorators/ember.ts diff --git a/changelog_unreleased/typescript/19492.md b/changelog_unreleased/typescript/19492.md new file mode 100644 index 000000000..72ca50d6d --- /dev/null +++ b/changelog_unreleased/typescript/19492.md @@ -0,0 +1,23 @@ +#### Allow decorators to be used with declare on class fields (#19492 by @evoactivity) + +Extensively used within the Ember ecosystem, decorators with `declare` on class fields will ignore the babel parser error and allow Prettier to format the code without breaking it. + + +```ts +// Input +export default class ProjectStatusComponent extends Component { + @service declare server: ServerService; +} + +// Prettier stable +// SyntaxError: Decorators can't be used with a declare field. (2:3) +// 1 | export default class ProjectStatusComponent extends Component { +//> 2 | @service declare server: ServerService; +// | ^ +// 3 | } + +// Prettier main +export default class ProjectStatusComponent extends Component { + @service declare server: ServerService; +} +``` diff --git a/src/language-js/parse/babel.js b/src/language-js/parse/babel.js index a8b7f959a..fc8c04124 100644 --- a/src/language-js/parse/babel.js +++ b/src/language-js/parse/babel.js @@ -222,6 +222,13 @@ const allowedReasonCodesArray = [ ``` */ "DeclarationMissingInitializer", + + /* + Allow decorators to be used with declare fields + https://github.com/prettier/prettier/issues/19491 + https://github.com/babel/babel/pull/17949 + */ + "DecoratorAbstractMethod", ]; const allowedReasonCodes = new Set(allowedReasonCodesArray); diff --git a/tests/format/typescript/comments/16065-2.ts b/tests/format/typescript/comments/16065-2.ts index 9917a4275..f63e84107 100644 --- a/tests/format/typescript/comments/16065-2.ts +++ b/tests/format/typescript/comments/16065-2.ts @@ -3,6 +3,14 @@ class Foo { @decorator readonly /* comment */ propertyDefinition; + // TSAbstractPropertyDefinition + @decorator + abstract /* comment */ abstractPropertyDefinition; + + // TSAbstractMethodDefinition + @decorator + abstract /* comment */ abstractMethodDefinition; + // MethodDefinition @decorator private /* comment */ methodDefinition() {} diff --git a/tests/format/typescript/comments/__snapshots__/format.test.js.snap b/tests/format/typescript/comments/__snapshots__/format.test.js.snap index 41d73e3b7..9f38d3caa 100644 --- a/tests/format/typescript/comments/__snapshots__/format.test.js.snap +++ b/tests/format/typescript/comments/__snapshots__/format.test.js.snap @@ -260,6 +260,14 @@ class Foo { @decorator readonly /* comment */ propertyDefinition; + // TSAbstractPropertyDefinition + @decorator + abstract /* comment */ abstractPropertyDefinition; + + // TSAbstractMethodDefinition + @decorator + abstract /* comment */ abstractMethodDefinition; + // MethodDefinition @decorator private /* comment */ methodDefinition() {} @@ -281,6 +289,14 @@ class Foo { @decorator readonly /* comment */ propertyDefinition; + // TSAbstractPropertyDefinition + @decorator + abstract /* comment */ abstractPropertyDefinition; + + // TSAbstractMethodDefinition + @decorator + abstract /* comment */ abstractMethodDefinition; + // MethodDefinition @decorator private /* comment */ methodDefinition() {} diff --git a/tests/format/typescript/decorators/__snapshots__/format.test.js.snap b/tests/format/typescript/decorators/__snapshots__/format.test.js.snap index acb96cb21..32bf67b7b 100644 --- a/tests/format/typescript/decorators/__snapshots__/format.test.js.snap +++ b/tests/format/typescript/decorators/__snapshots__/format.test.js.snap @@ -292,6 +292,12 @@ class Something { readonly property: Array } +class Something2 { + @foo() + // comment + abstract property: Array +} + =====================================output===================================== class Foo1 { @foo @@ -323,6 +329,43 @@ class Something { readonly property: Array; } +class Something2 { + @foo() + // comment + abstract property: Array; +} + +================================================================================ +`; + +exports[`ember.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript"] + printWidth: 80 (default) | +=====================================input====================================== +/* +https://github.com/prettier/prettier/issues/19491 + +decorators with declare is used extensively in Ember ecosystem, +and we want to make sure that Prettier doesn't break them. +*/ + +export default class ProjectStatusComponent extends Component { + @service declare server: ServerService; +} + +=====================================output===================================== +/* +https://github.com/prettier/prettier/issues/19491 + +decorators with declare is used extensively in Ember ecosystem, +and we want to make sure that Prettier doesn't break them. +*/ + +export default class ProjectStatusComponent extends Component { + @service declare server: ServerService; +} + ================================================================================ `; diff --git a/tests/format/typescript/decorators/decorators-comments.ts b/tests/format/typescript/decorators/decorators-comments.ts index b3158563d..2f3c378a9 100644 --- a/tests/format/typescript/decorators/decorators-comments.ts +++ b/tests/format/typescript/decorators/decorators-comments.ts @@ -28,3 +28,9 @@ class Something { // comment readonly property: Array } + +class Something2 { + @foo() + // comment + abstract property: Array +} diff --git a/tests/format/typescript/decorators/ember.ts b/tests/format/typescript/decorators/ember.ts new file mode 100644 index 000000000..7b94dc883 --- /dev/null +++ b/tests/format/typescript/decorators/ember.ts @@ -0,0 +1,10 @@ +/* +https://github.com/prettier/prettier/issues/19491 + +decorators with declare is used extensively in Ember ecosystem, +and we want to make sure that Prettier doesn't break them. +*/ + +export default class ProjectStatusComponent extends Component { + @service declare server: ServerService; +} From 3732e1dee6a36bdb2e77a722d206a79ac7e67aa3 Mon Sep 17 00:00:00 2001 From: fisker Date: Mon, 29 Jun 2026 19:34:41 +0800 Subject: [PATCH 3/5] Release 3.9.3 --- .github/ISSUE_TEMPLATE/formatting.md | 2 +- .github/ISSUE_TEMPLATE/integration.md | 2 +- CHANGELOG.md | 46 +++++++++++++++++++ package.json | 2 +- .../versioned_docs/version-stable/browser.md | 36 +++++++-------- .../versioned_docs/version-stable/install.md | 10 ++-- .../version-stable/sharing-configurations.md | 4 +- 7 files changed, 74 insertions(+), 28 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/formatting.md b/.github/ISSUE_TEMPLATE/formatting.md index 5d6d3a27f..fa46e7a0d 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.9.1** +**Prettier 3.9.3** [Playground link](https://prettier.io/playground/#.....) ```sh diff --git a/.github/ISSUE_TEMPLATE/integration.md b/.github/ISSUE_TEMPLATE/integration.md index 0cdaaa6ec..a917eafcf 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.9.1 +- Prettier Version: 3.9.3 - Running Prettier via: - Runtime: - Operating System: diff --git a/CHANGELOG.md b/CHANGELOG.md index 889a1f80d..794b314db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +# 3.9.3 + +[diff](https://github.com/prettier/prettier/compare/3.9.1...3.9.3) + +#### Markdown: Fix unexpected removal of characters in liquid syntax ([#19489](https://github.com/prettier/prettier/pull/19489) by [@seiyab](https://github.com/seiyab)) + + +```md +// Input + +{{ page.title +}} text + + +{{ page.title + text + + +{{ page.title +}} text +``` + +#### TypeScript: Allow decorators to be used with declare on class fields ([#19492](https://github.com/prettier/prettier/pull/19492) by [@evoactivity](https://github.com/evoactivity)) + +Extensively used within the Ember ecosystem, decorators with `declare` on class fields will ignore the babel parser error and allow Prettier to format the code without breaking it. + + +```ts +// Input +export default class ProjectStatusComponent extends Component { + @service declare server: ServerService; +} + +// Prettier 3.9.1 +// SyntaxError: Decorators can't be used with a declare field. (2:3) +// 1 | export default class ProjectStatusComponent extends Component { +//> 2 | @service declare server: ServerService; +// | ^ +// 3 | } + +// Prettier 3.9.3 +export default class ProjectStatusComponent extends Component { + @service declare server: ServerService; +} +``` + # 3.9.1 [diff](https://github.com/prettier/prettier/compare/3.9.0...3.9.1) diff --git a/package.json b/package.json index 0fd179ae8..1c1873a5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prettier", - "version": "3.10.0-dev", + "version": "3.9.3", "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 aeb13c183..c98038389 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.9.1/plugins](https://unpkg.com/browse/prettier@3.9.1/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.9.3/plugins](https://unpkg.com/browse/prettier@3.9.3/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 - - + +