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..116e42a8 --- /dev/null +++ b/tests/issues/issue-532/formatted.pug @@ -0,0 +1,41 @@ +//- 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; + } + +//- 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/issue-532.test.ts b/tests/issues/issue-532/issue-532.test.ts new file mode 100644 index 00000000..b234f0d2 --- /dev/null +++ b/tests/issues/issue-532/issue-532.test.ts @@ -0,0 +1,19 @@ +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); + }); + + 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 diff --git a/tests/issues/issue-532/unformatted.pug b/tests/issues/issue-532/unformatted.pug new file mode 100644 index 00000000..81ff2ced --- /dev/null +++ b/tests/issues/issue-532/unformatted.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 } + +//- 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 }