From 2a302236478aa68e9d159c5af9e69d200b73ec55 Mon Sep 17 00:00:00 2001 From: Blake Miner Date: Tue, 29 Sep 2026 09:51:19 -0400 Subject: [PATCH 1/3] Respect style tag type attribute when formatting Choose the parser for `style` blocks from the `type` attribute instead of always using `css`. `scss` and `less` types use their prettier parsers, and unsupported types such as `sass` are left unformatted. Closes #532 Co-Authored-By: Claude Opus 5.5 --- src/printer.ts | 5 ++- src/utils/style-mime-types.ts | 46 ++++++++++++++++++++++++ tests/issues/issue-532/formatted.pug | 31 ++++++++++++++++ tests/issues/issue-532/issue-532.test.ts | 11 ++++++ tests/issues/issue-532/unformatted.pug | 23 ++++++++++++ 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/utils/style-mime-types.ts create mode 100644 tests/issues/issue-532/formatted.pug create mode 100644 tests/issues/issue-532/issue-532.test.ts create mode 100644 tests/issues/issue-532/unformatted.pug diff --git a/src/printer.ts b/src/printer.ts index 90d68938..93a25f40 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -97,6 +97,7 @@ import { unwrapLineFeeds, } from './utils/common'; import { getScriptParserName } from './utils/script-mime-types'; +import { getStyleParserName } from './utils/style-mime-types'; import { isSvelteInterpolation } from './utils/svelte'; import { isVueEventBinding, @@ -1994,7 +1995,9 @@ export class PugPrinter { } case 'style': { - parser = 'css'; + parser = getStyleParserName( + previousTypeAttributeToken(this.tokens, this.currentIndex), + ); break; } diff --git a/src/utils/style-mime-types.ts b/src/utils/style-mime-types.ts new file mode 100644 index 00000000..6e878a55 --- /dev/null +++ b/src/utils/style-mime-types.ts @@ -0,0 +1,46 @@ +import type { BuiltInParserName } from 'prettier'; +import type { AttributeToken } from 'pug-lexer'; + +const wrappingQuotesRe: RegExp = /(^(["'`]))|((["'`])$)/g; + +// Matches style types to the required parser for them +// Note: Types not listed here (e.g. `sass` or `stylus`) are left unformatted, +// because there is no builtin prettier parser for them +const styleTypeToParserMap: Map = new Map([ + ['css', 'css'], + ['text/css', 'css'], + ['less', 'less'], + ['text/less', 'less'], + ['scss', 'scss'], + ['text/scss', 'scss'], +]); + +/** + * Decides which parser to format style contents with. + * + * @param typeAttrToken Type token of the style tag. + * @returns Parser name to parse contents with. + */ +export function getStyleParserName( + typeAttrToken?: AttributeToken, +): BuiltInParserName | undefined { + // Omission means CSS + if (!typeAttrToken) { + return 'css'; + } + + const typeRaw: string | boolean = typeAttrToken.val; + // If it's not a string, best not do anything + if (typeof typeRaw !== 'string') { + return; + } + + const type: string = typeRaw.replaceAll(wrappingQuotesRe, '').toLowerCase(); + + // Empty type is equivalent to omission + if (!type) { + return 'css'; + } + + return styleTypeToParserMap.get(type); +} diff --git a/tests/issues/issue-532/formatted.pug b/tests/issues/issue-532/formatted.pug new file mode 100644 index 00000000..9c003b6b --- /dev/null +++ b/tests/issues/issue-532/formatted.pug @@ -0,0 +1,31 @@ +//- Stays unchanged, because no loaded plugin provides a parser for sass +style(type="sass"). + :host + display: inline + +//- Formatted with the builtin scss parser +style(type="text/scss"). + :host { + .a { + display: inline; + } + } + +//- Formatted with the builtin less parser +style(type="less"). + @color: red; + .a { + color: @color; + } + +//- Formatted with the builtin css parser +style(type="text/css"). + :host { + display: inline; + } + +//- Without a type, formatted with the builtin css parser +style. + :host { + display: inline; + } diff --git a/tests/issues/issue-532/issue-532.test.ts b/tests/issues/issue-532/issue-532.test.ts new file mode 100644 index 00000000..9ce64533 --- /dev/null +++ b/tests/issues/issue-532/issue-532.test.ts @@ -0,0 +1,11 @@ +import { compareFiles } from 'tests/common'; +import { describe, expect, it } from 'vitest'; + +describe('Issues', () => { + describe('issue #532', () => { + it('should format style tags based on their type attribute', async () => { + const { actual, expected } = await compareFiles(import.meta.url); + expect(actual).toBe(expected); + }); + }); +}); diff --git a/tests/issues/issue-532/unformatted.pug b/tests/issues/issue-532/unformatted.pug new file mode 100644 index 00000000..2a275e04 --- /dev/null +++ b/tests/issues/issue-532/unformatted.pug @@ -0,0 +1,23 @@ +//- Stays unchanged, because no loaded plugin provides a parser for sass +style(type="sass"). + :host + display: inline + +//- Formatted with the builtin scss parser +style(type='text/scss'). + :host { + .a { display:inline } + } + +//- Formatted with the builtin less parser +style(type="less"). + @color: red; + .a { color:@color } + +//- Formatted with the builtin css parser +style(type="text/css"). + :host { display:inline } + +//- Without a type, formatted with the builtin css parser +style. + :host { display:inline } From 52b5c9a4e70256d4ff66e1fd6713574a57df4ac1 Mon Sep 17 00:00:00 2001 From: Blake Miner Date: Tue, 29 Sep 2026 12:47:57 -0400 Subject: [PATCH 2/3] Cover empty and valueless style types in tests Co-Authored-By: Claude Opus 5.5 --- tests/issues/issue-532/formatted.pug | 10 ++++++++++ tests/issues/issue-532/unformatted.pug | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/tests/issues/issue-532/formatted.pug b/tests/issues/issue-532/formatted.pug index 9c003b6b..116e42a8 100644 --- a/tests/issues/issue-532/formatted.pug +++ b/tests/issues/issue-532/formatted.pug @@ -29,3 +29,13 @@ style. :host { display: inline; } + +//- An empty type is formatted with the builtin css parser +style(type=""). + :host { + display: inline; + } + +//- Stays unchanged, because a type without a value is not a string +style(type). + :host { display:inline } diff --git a/tests/issues/issue-532/unformatted.pug b/tests/issues/issue-532/unformatted.pug index 2a275e04..81ff2ced 100644 --- a/tests/issues/issue-532/unformatted.pug +++ b/tests/issues/issue-532/unformatted.pug @@ -21,3 +21,11 @@ style(type="text/css"). //- Without a type, formatted with the builtin css parser style. :host { display:inline } + +//- An empty type is formatted with the builtin css parser +style(type=""). + :host { display:inline } + +//- Stays unchanged, because a type without a value is not a string +style(type). + :host { display:inline } From 044f6cd405c1bca2619e996956dbb2767b881491 Mon Sep 17 00:00:00 2001 From: Blake Miner Date: Thu, 1 Oct 2026 14:23:20 -0400 Subject: [PATCH 3/3] Cover empty and valueless script types in tests Co-Authored-By: Claude Opus 5.5 --- tests/issues/issue-532/issue-532.test.ts | 8 ++++++++ tests/issues/issue-532/script-types.formatted.pug | 7 +++++++ tests/issues/issue-532/script-types.unformatted.pug | 7 +++++++ 3 files changed, 22 insertions(+) create mode 100644 tests/issues/issue-532/script-types.formatted.pug create mode 100644 tests/issues/issue-532/script-types.unformatted.pug diff --git a/tests/issues/issue-532/issue-532.test.ts b/tests/issues/issue-532/issue-532.test.ts index 9ce64533..b234f0d2 100644 --- a/tests/issues/issue-532/issue-532.test.ts +++ b/tests/issues/issue-532/issue-532.test.ts @@ -7,5 +7,13 @@ describe('Issues', () => { const { actual, expected } = await compareFiles(import.meta.url); expect(actual).toBe(expected); }); + + it('should handle empty and valueless script types like style types', async () => { + const { actual, expected } = await compareFiles(import.meta.url, { + source: 'script-types.unformatted.pug', + target: 'script-types.formatted.pug', + }); + expect(actual).toBe(expected); + }); }); }); diff --git a/tests/issues/issue-532/script-types.formatted.pug b/tests/issues/issue-532/script-types.formatted.pug new file mode 100644 index 00000000..06ec31cb --- /dev/null +++ b/tests/issues/issue-532/script-types.formatted.pug @@ -0,0 +1,7 @@ +//- An empty type is formatted with the builtin babel parser +script(type=""). + const a = 1; + +//- Stays unchanged, because a type without a value is not a string +script(type). + const a = 1 diff --git a/tests/issues/issue-532/script-types.unformatted.pug b/tests/issues/issue-532/script-types.unformatted.pug new file mode 100644 index 00000000..1dc519c1 --- /dev/null +++ b/tests/issues/issue-532/script-types.unformatted.pug @@ -0,0 +1,7 @@ +//- An empty type is formatted with the builtin babel parser +script(type=""). + const a = 1 + +//- Stays unchanged, because a type without a value is not a string +script(type). + const a = 1