Skip to content

Commit f0aa522

Browse files
MrHailaShinigami92
andauthored
Multiple fixes for class attribute conversions (#509)
Co-authored-by: Shinigami92 <chrissi92@hotmail.de>
1 parent e3fc15c commit f0aa522

6 files changed

Lines changed: 231 additions & 11 deletions

File tree

src/printer.ts

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,24 +1274,73 @@ export class PugPrinter {
12741274
private class(token: ClassToken): void {
12751275
if (this.options.pugClassNotation === 'attribute') {
12761276
this.classLiteralToAttribute.push(token.val);
1277+
1278+
// An extra div should be printed if...
12771279
if (
1278-
this.previousToken?.type !== 'tag' &&
1279-
this.previousToken?.type !== 'class'
1280+
this.previousToken === undefined ||
1281+
// ...the previous token indicates that this was the first class literal and thus a div did not previously exist...
1282+
this.checkTokenType(
1283+
this.previousToken,
1284+
['tag', 'class', 'end-attributes'],
1285+
true,
1286+
) ||
1287+
// ...OR the previous token is a div that will be removed because of the no explicit divs rule.
1288+
(this.previousToken.type === 'tag' &&
1289+
this.previousToken.val === 'div' &&
1290+
this.nextToken?.type !== 'attribute' &&
1291+
!this.options.pugExplicitDiv)
12801292
) {
12811293
this.result += `${this.computedIndent}div`;
12821294
}
1295+
12831296
if (
1284-
this.nextToken &&
1285-
['text', 'newline', 'indent', 'outdent', 'eos'].includes(
1286-
this.nextToken.type,
1287-
)
1297+
this.checkTokenType(this.nextToken, [
1298+
'text',
1299+
'newline',
1300+
'indent',
1301+
'outdent',
1302+
'eos',
1303+
':',
1304+
])
12881305
) {
1306+
// Copy and clear the class literals list.
12891307
const classes: string[] = this.classLiteralToAttribute.splice(
12901308
0,
12911309
this.classLiteralToAttribute.length,
12921310
);
1293-
this.result += `(class=${this.quoteString(classes.join(' '))})`;
1294-
if (this.nextToken.type === 'text') {
1311+
1312+
// If the last result character was a )...
1313+
if (this.result.at(-1) === ')') {
1314+
// Look for 'class=' that is before the last '('...
1315+
const attributesStartIndex: number = this.result.lastIndexOf('(');
1316+
const lastClassIndex: number = this.result.indexOf(
1317+
'class=',
1318+
attributesStartIndex,
1319+
);
1320+
1321+
// If a 'class=' is found...
1322+
// eslint-disable-next-line unicorn/prefer-ternary -- This is more readable without ternaries.
1323+
if (lastClassIndex > -1) {
1324+
// ...then insert the new class into it.
1325+
this.result = [
1326+
this.result.slice(0, lastClassIndex + 7),
1327+
classes.join(' '),
1328+
' ',
1329+
this.result.slice(lastClassIndex + 7),
1330+
].join('');
1331+
} else {
1332+
// ...otherwise add a new class attribute into the existing attributes.
1333+
this.result =
1334+
this.result.slice(0, -1) +
1335+
`${this.neverUseAttributeSeparator ? ' ' : ', '}class=${this.quoteString(classes.join(' '))})`;
1336+
}
1337+
// ...or if the element has no attributes...
1338+
} else {
1339+
// Start a new attribute list with the class attribute in it.
1340+
this.result += `(class=${this.quoteString(classes.join(' '))})`;
1341+
}
1342+
1343+
if (this.nextToken?.type === 'text') {
12951344
this.result += ' ';
12961345
}
12971346
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
input.bar.baz(class="foo")
2+
input.bar.baz(
3+
readonly,
4+
class="foo",
5+
name="test-input",
6+
type="password",
7+
value="Hello World"
8+
)
9+
.foo bar
10+
.foo(bar="baz") Hello World
11+
.foo
12+
div(class="bar") baz
13+
.baz
14+
.bar foo
15+
- var code = "ma-2 px-1";
16+
div(class=code)
17+
.foo
18+
div.foo(attribute="value")
19+
div.foo(class="bar", attribute="value")
20+
div.foo.baz(attribute="value", class="bar")
21+
div.foo.baz(attribute="value", class="bar")
22+
.foo.baz(attribute="value", class="bar")
23+
div.bar(class="foo"): span Text
24+
div: span.foo
25+
div: span(class="foo")
26+
.foo: span.bar.foo(class="baz")
27+
.foo.baz(class="bar"): h1.foo.baz(class="bar") Let's really #[span.foo.baz(class="bar") get nasty]!
28+
.indent
29+
input.bar.baz(class="foo")
30+
input.bar.baz(
31+
readonly,
32+
class="foo",
33+
name="test-input",
34+
type="password",
35+
value="Hello World"
36+
)
37+
.foo bar
38+
.foo(bar="baz") Hello World
39+
.foo
40+
div(class="bar") baz
41+
.baz
42+
.bar foo
43+
- var code = "ma-2 px-1";
44+
div(class=code)
45+
.foo
46+
div.foo(attribute="value")
47+
div.foo(class="bar", attribute="value")
48+
div.foo.baz(attribute="value", class="bar")
49+
div.foo.baz(attribute="value", class="bar")
50+
.foo.baz(attribute="value", class="bar")
51+
div.bar(class="foo"): span Text
52+
div: span.foo
53+
div: span(class="foo")
54+
.foo: span.bar.foo(class="baz")
55+
.foo.baz(class="bar"): h1.foo.baz(class="bar") Let's really #[span.foo.baz(class="bar") get nasty]!

tests/options/pugClassNotation/formatted-attribute.pug

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,36 @@ div(class="baz")
88
div(class="bar") foo
99
- var code = "ma-2 px-1";
1010
div(class=code)
11+
div(class="foo")
12+
div(attribute="value", class="foo")
13+
div(class="foo bar", attribute="value")
14+
div(attribute="value", class="foo baz bar")
15+
div(attribute="value", class="foo baz bar")
16+
div(attribute="value", class="baz foo bar")
17+
div(class="bar foo"): span Text
18+
div: span(class="foo")
19+
div: span(class="foo")
20+
div(class="foo"): span(class="foo bar baz")
21+
div(class="baz foo bar"): h1(class="baz foo bar") Let's really #[span(class="baz foo bar") get nasty]!
22+
div(class="indent")
23+
input(class="bar baz foo")
24+
input(readonly, class="bar baz foo", name="test-input", type="password", value="Hello World")
25+
div(class="foo") bar
26+
div(bar="baz" class="foo") Hello World
27+
div(class="foo")
28+
div(class="bar") baz
29+
div(class="baz")
30+
div(class="bar") foo
31+
- var code = "ma-2 px-1";
32+
div(class=code)
33+
div(class="foo")
34+
div(attribute="value", class="foo")
35+
div(class="foo bar", attribute="value")
36+
div(attribute="value", class="foo baz bar")
37+
div(attribute="value", class="foo baz bar")
38+
div(attribute="value", class="baz foo bar")
39+
div(class="bar foo"): span Text
40+
div: span(class="foo")
41+
div: span(class="foo")
42+
div(class="foo"): span(class="foo bar baz")
43+
div(class="baz foo bar"): h1(class="baz foo bar") Let's really #[span(class="baz foo bar") get nasty]!

tests/options/pugClassNotation/formatted-literal.pug

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,41 @@ input.bar.baz.foo(
1313
.bar foo
1414
- var code = "ma-2 px-1";
1515
div(class=code)
16+
.foo
17+
div.foo(attribute="value")
18+
.bar.foo(attribute="value")
19+
.bar.foo.baz(attribute="value")
20+
.bar.foo.baz(attribute="value")
21+
.foo.bar.baz(attribute="value")
22+
.foo.bar: span Text
23+
div: span.foo
24+
div: span.foo
25+
.foo: span.bar.baz.foo
26+
.foo.bar.baz: h1.foo.bar.baz Let's really #[span.foo.bar.baz get nasty]!
27+
.indent
28+
input.bar.baz.foo
29+
input.bar.baz.foo(
30+
readonly,
31+
name="test-input",
32+
type="password",
33+
value="Hello World"
34+
)
35+
.foo bar
36+
.foo(bar="baz") Hello World
37+
.foo
38+
.bar baz
39+
.baz
40+
.bar foo
41+
- var code = "ma-2 px-1";
42+
div(class=code)
43+
.foo
44+
div.foo(attribute="value")
45+
.bar.foo(attribute="value")
46+
.bar.foo.baz(attribute="value")
47+
.bar.foo.baz(attribute="value")
48+
.foo.bar.baz(attribute="value")
49+
.foo.bar: span Text
50+
div: span.foo
51+
div: span.foo
52+
.foo: span.bar.baz.foo
53+
.foo.bar.baz: h1.foo.bar.baz Let's really #[span.foo.bar.baz get nasty]!

tests/options/pugClassNotation/pug-class-notation.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import { describe, expect, it } from 'vitest';
44
describe('Options', () => {
55
describe('pugClassNotation', () => {
66
it('should keep classes as is', async () => {
7-
const { actual, code } = await compareFiles(import.meta.url, {
8-
target: null,
7+
const { actual, expected } = await compareFiles(import.meta.url, {
8+
target: 'formatted-as-is.pug',
99
formatOptions: {
1010
pugClassNotation: 'as-is',
1111
},
1212
});
13-
expect(actual).toBe(code);
13+
expect(actual).toBe(expected);
1414
});
1515

1616
it('should keep classes as literals', async () => {

tests/options/pugClassNotation/unformatted.pug

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,48 @@ input.bar.baz(
1414
.bar foo
1515
- var code = "ma-2 px-1";
1616
div(class=code)
17+
div.foo
18+
div(attribute="value").foo
19+
div(class="bar", attribute="value").foo
20+
div(attribute="value", class="bar").foo.baz
21+
div(
22+
attribute="value"
23+
class="bar"
24+
).foo.baz
25+
div.foo(attribute="value" class="bar").baz
26+
div(class="foo").bar: span Text
27+
div: span.foo
28+
div: span(class="foo")
29+
div.foo: span.bar(class="baz").foo
30+
div.foo(class="bar").baz: h1.foo(class="bar").baz Let's really #[span.foo(class="bar").baz get nasty]!
31+
.indent
32+
input.bar.baz(class="foo")
33+
input.bar.baz(
34+
readonly,
35+
class="foo",
36+
name="test-input",
37+
type="password",
38+
value="Hello World"
39+
)
40+
.foo bar
41+
.foo(bar="baz") Hello World
42+
.foo
43+
div(class="bar") baz
44+
.baz
45+
.bar foo
46+
- var code = "ma-2 px-1";
47+
div(class=code)
48+
div.foo
49+
div(attribute="value").foo
50+
div(class="bar", attribute="value").foo
51+
div(attribute="value", class="bar").foo.baz
52+
div(
53+
attribute="value"
54+
class="bar"
55+
).foo.baz
56+
div.foo(attribute="value" class="bar").baz
57+
div(class="foo").bar: span Text
58+
div: span.foo
59+
div: span(class="foo")
60+
div.foo: span.bar(class="baz").foo
61+
div.foo(class="bar").baz: h1.foo(class="bar").baz Let's really #[span.foo(class="bar").baz get nasty]!

0 commit comments

Comments
 (0)