diff --git a/src/printer.ts b/src/printer.ts index 1a474021..aa6b8ace 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -1274,24 +1274,73 @@ export class PugPrinter { private class(token: ClassToken): void { if (this.options.pugClassNotation === 'attribute') { this.classLiteralToAttribute.push(token.val); + + // An extra div should be printed if... if ( - this.previousToken?.type !== 'tag' && - this.previousToken?.type !== 'class' + this.previousToken === undefined || + // ...the previous token indicates that this was the first class literal and thus a div did not previously exist... + this.checkTokenType( + this.previousToken, + ['tag', 'class', 'end-attributes'], + true, + ) || + // ...OR the previous token is a div that will be removed because of the no explicit divs rule. + (this.previousToken.type === 'tag' && + this.previousToken.val === 'div' && + this.nextToken?.type !== 'attribute' && + !this.options.pugExplicitDiv) ) { this.result += `${this.computedIndent}div`; } + if ( - this.nextToken && - ['text', 'newline', 'indent', 'outdent', 'eos'].includes( - this.nextToken.type, - ) + this.checkTokenType(this.nextToken, [ + 'text', + 'newline', + 'indent', + 'outdent', + 'eos', + ':', + ]) ) { + // Copy and clear the class literals list. const classes: string[] = this.classLiteralToAttribute.splice( 0, this.classLiteralToAttribute.length, ); - this.result += `(class=${this.quoteString(classes.join(' '))})`; - if (this.nextToken.type === 'text') { + + // If the last result character was a )... + if (this.result.at(-1) === ')') { + // Look for 'class=' that is before the last '('... + const attributesStartIndex: number = this.result.lastIndexOf('('); + const lastClassIndex: number = this.result.indexOf( + 'class=', + attributesStartIndex, + ); + + // If a 'class=' is found... + // eslint-disable-next-line unicorn/prefer-ternary -- This is more readable without ternaries. + if (lastClassIndex > -1) { + // ...then insert the new class into it. + this.result = [ + this.result.slice(0, lastClassIndex + 7), + classes.join(' '), + ' ', + this.result.slice(lastClassIndex + 7), + ].join(''); + } else { + // ...otherwise add a new class attribute into the existing attributes. + this.result = + this.result.slice(0, -1) + + `${this.neverUseAttributeSeparator ? ' ' : ', '}class=${this.quoteString(classes.join(' '))})`; + } + // ...or if the element has no attributes... + } else { + // Start a new attribute list with the class attribute in it. + this.result += `(class=${this.quoteString(classes.join(' '))})`; + } + + if (this.nextToken?.type === 'text') { this.result += ' '; } } diff --git a/tests/options/pugClassNotation/formatted-as-is.pug b/tests/options/pugClassNotation/formatted-as-is.pug new file mode 100644 index 00000000..d7646a58 --- /dev/null +++ b/tests/options/pugClassNotation/formatted-as-is.pug @@ -0,0 +1,55 @@ +input.bar.baz(class="foo") +input.bar.baz( + readonly, + class="foo", + name="test-input", + type="password", + value="Hello World" +) +.foo bar +.foo(bar="baz") Hello World +.foo + div(class="bar") baz +.baz +.bar foo +- var code = "ma-2 px-1"; +div(class=code) +.foo +div.foo(attribute="value") +div.foo(class="bar", attribute="value") +div.foo.baz(attribute="value", class="bar") +div.foo.baz(attribute="value", class="bar") +.foo.baz(attribute="value", class="bar") +div.bar(class="foo"): span Text +div: span.foo +div: span(class="foo") +.foo: span.bar.foo(class="baz") +.foo.baz(class="bar"): h1.foo.baz(class="bar") Let's really #[span.foo.baz(class="bar") get nasty]! +.indent + input.bar.baz(class="foo") + input.bar.baz( + readonly, + class="foo", + name="test-input", + type="password", + value="Hello World" + ) + .foo bar + .foo(bar="baz") Hello World + .foo + div(class="bar") baz + .baz + .bar foo + - var code = "ma-2 px-1"; + div(class=code) + .foo + div.foo(attribute="value") + div.foo(class="bar", attribute="value") + div.foo.baz(attribute="value", class="bar") + div.foo.baz(attribute="value", class="bar") + .foo.baz(attribute="value", class="bar") + div.bar(class="foo"): span Text + div: span.foo + div: span(class="foo") + .foo: span.bar.foo(class="baz") + .foo.baz(class="bar"): h1.foo.baz(class="bar") Let's really #[span.foo.baz(class="bar") get nasty]! diff --git a/tests/options/pugClassNotation/formatted-attribute.pug b/tests/options/pugClassNotation/formatted-attribute.pug index b13d68ef..86814666 100644 --- a/tests/options/pugClassNotation/formatted-attribute.pug +++ b/tests/options/pugClassNotation/formatted-attribute.pug @@ -8,3 +8,36 @@ div(class="baz") div(class="bar") foo - var code = "ma-2 px-1"; div(class=code) +div(class="foo") +div(attribute="value", class="foo") +div(class="foo bar", attribute="value") +div(attribute="value", class="foo baz bar") +div(attribute="value", class="foo baz bar") +div(attribute="value", class="baz foo bar") +div(class="bar foo"): span Text +div: span(class="foo") +div: span(class="foo") +div(class="foo"): span(class="foo bar baz") +div(class="baz foo bar"): h1(class="baz foo bar") Let's really #[span(class="baz foo bar") get nasty]! +div(class="indent") + input(class="bar baz foo") + input(readonly, class="bar baz foo", name="test-input", type="password", value="Hello World") + div(class="foo") bar + div(bar="baz" class="foo") Hello World + div(class="foo") + div(class="bar") baz + div(class="baz") + div(class="bar") foo + - var code = "ma-2 px-1"; + div(class=code) + div(class="foo") + div(attribute="value", class="foo") + div(class="foo bar", attribute="value") + div(attribute="value", class="foo baz bar") + div(attribute="value", class="foo baz bar") + div(attribute="value", class="baz foo bar") + div(class="bar foo"): span Text + div: span(class="foo") + div: span(class="foo") + div(class="foo"): span(class="foo bar baz") + div(class="baz foo bar"): h1(class="baz foo bar") Let's really #[span(class="baz foo bar") get nasty]! diff --git a/tests/options/pugClassNotation/formatted-literal.pug b/tests/options/pugClassNotation/formatted-literal.pug index b0af196f..76eb9eea 100644 --- a/tests/options/pugClassNotation/formatted-literal.pug +++ b/tests/options/pugClassNotation/formatted-literal.pug @@ -13,3 +13,41 @@ input.bar.baz.foo( .bar foo - var code = "ma-2 px-1"; div(class=code) +.foo +div.foo(attribute="value") +.bar.foo(attribute="value") +.bar.foo.baz(attribute="value") +.bar.foo.baz(attribute="value") +.foo.bar.baz(attribute="value") +.foo.bar: span Text +div: span.foo +div: span.foo +.foo: span.bar.baz.foo +.foo.bar.baz: h1.foo.bar.baz Let's really #[span.foo.bar.baz get nasty]! +.indent + input.bar.baz.foo + input.bar.baz.foo( + readonly, + name="test-input", + type="password", + value="Hello World" + ) + .foo bar + .foo(bar="baz") Hello World + .foo + .bar baz + .baz + .bar foo + - var code = "ma-2 px-1"; + div(class=code) + .foo + div.foo(attribute="value") + .bar.foo(attribute="value") + .bar.foo.baz(attribute="value") + .bar.foo.baz(attribute="value") + .foo.bar.baz(attribute="value") + .foo.bar: span Text + div: span.foo + div: span.foo + .foo: span.bar.baz.foo + .foo.bar.baz: h1.foo.bar.baz Let's really #[span.foo.bar.baz get nasty]! diff --git a/tests/options/pugClassNotation/pug-class-notation.test.ts b/tests/options/pugClassNotation/pug-class-notation.test.ts index ea76a6df..4c842b5e 100644 --- a/tests/options/pugClassNotation/pug-class-notation.test.ts +++ b/tests/options/pugClassNotation/pug-class-notation.test.ts @@ -4,13 +4,13 @@ import { describe, expect, it } from 'vitest'; describe('Options', () => { describe('pugClassNotation', () => { it('should keep classes as is', async () => { - const { actual, code } = await compareFiles(import.meta.url, { - target: null, + const { actual, expected } = await compareFiles(import.meta.url, { + target: 'formatted-as-is.pug', formatOptions: { pugClassNotation: 'as-is', }, }); - expect(actual).toBe(code); + expect(actual).toBe(expected); }); it('should keep classes as literals', async () => { diff --git a/tests/options/pugClassNotation/unformatted.pug b/tests/options/pugClassNotation/unformatted.pug index 9b0b78b2..1b47c501 100644 --- a/tests/options/pugClassNotation/unformatted.pug +++ b/tests/options/pugClassNotation/unformatted.pug @@ -14,3 +14,48 @@ input.bar.baz( .bar foo - var code = "ma-2 px-1"; div(class=code) +div.foo +div(attribute="value").foo +div(class="bar", attribute="value").foo +div(attribute="value", class="bar").foo.baz +div( + attribute="value" + class="bar" +).foo.baz +div.foo(attribute="value" class="bar").baz +div(class="foo").bar: span Text +div: span.foo +div: span(class="foo") +div.foo: span.bar(class="baz").foo +div.foo(class="bar").baz: h1.foo(class="bar").baz Let's really #[span.foo(class="bar").baz get nasty]! +.indent + input.bar.baz(class="foo") + input.bar.baz( + readonly, + class="foo", + name="test-input", + type="password", + value="Hello World" + ) + .foo bar + .foo(bar="baz") Hello World + .foo + div(class="bar") baz + .baz + .bar foo + - var code = "ma-2 px-1"; + div(class=code) + div.foo + div(attribute="value").foo + div(class="bar", attribute="value").foo + div(attribute="value", class="bar").foo.baz + div( + attribute="value" + class="bar" + ).foo.baz + div.foo(attribute="value" class="bar").baz + div(class="foo").bar: span Text + div: span.foo + div: span(class="foo") + div.foo: span.bar(class="baz").foo + div.foo(class="bar").baz: h1.foo(class="bar").baz Let's really #[span.foo(class="bar").baz get nasty]!