Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple fixes for class attribute conversions #509

Merged
merged 8 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 57 additions & 8 deletions src/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 += ' ';
}
}
Expand Down
55 changes: 55 additions & 0 deletions tests/options/pugClassNotation/formatted-as-is.pug
Original file line number Diff line number Diff line change
@@ -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]!
33 changes: 33 additions & 0 deletions tests/options/pugClassNotation/formatted-attribute.pug
Original file line number Diff line number Diff line change
Expand Up @@ -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]!
38 changes: 38 additions & 0 deletions tests/options/pugClassNotation/formatted-literal.pug
Original file line number Diff line number Diff line change
Expand Up @@ -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]!
6 changes: 3 additions & 3 deletions tests/options/pugClassNotation/pug-class-notation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
45 changes: 45 additions & 0 deletions tests/options/pugClassNotation/unformatted.pug
Original file line number Diff line number Diff line change
Expand Up @@ -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]!
Loading