Skip to content

Commit 593c79b

Browse files
regsebcoliff
andauthored
feat(attr-sorted): Sort data-* attributes at end. (#1591)
* feat(attr-sorted): Sort data-* attributes at end. * Rename "lambda" to "regular". --------- Co-authored-by: Christian Oliff <[email protected]>
1 parent 01e723e commit 593c79b

File tree

5 files changed

+127
-31
lines changed

5 files changed

+127
-31
lines changed

dist/core/rules/attr-sorted.js

Lines changed: 15 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/htmlhint.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -484,16 +484,25 @@
484484
}
485485
const originalAttrs = JSON.stringify(listOfAttributes);
486486
listOfAttributes.sort((a, b) => {
487-
if (orderMap[a] == undefined && orderMap[b] == undefined) {
488-
return a.localeCompare(b);
487+
if (orderMap[a] !== undefined) {
488+
if (orderMap[b] !== undefined) {
489+
return orderMap[a] - orderMap[b];
490+
}
491+
return -1;
492+
}
493+
if (a.startsWith('data-')) {
494+
if (b.startsWith('data-')) {
495+
return a.localeCompare(b);
496+
}
497+
return 1;
489498
}
490-
if (orderMap[a] == undefined) {
499+
if (orderMap[b] !== undefined) {
491500
return 1;
492501
}
493-
else if (orderMap[b] == undefined) {
502+
if (b.startsWith('data-')) {
494503
return -1;
495504
}
496-
return orderMap[a] - orderMap[b] || a.localeCompare(b);
505+
return a.localeCompare(b);
497506
});
498507
if (originalAttrs !== JSON.stringify(listOfAttributes)) {
499508
reporter.error(`Inaccurate order ${originalAttrs} should be in hierarchy ${JSON.stringify(listOfAttributes)} `, event.line, event.col, this, event.raw);

dist/htmlhint.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/rules/attr-sorted.ts

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,37 @@ export default {
3434

3535
const originalAttrs = JSON.stringify(listOfAttributes)
3636
listOfAttributes.sort((a, b) => {
37-
if (orderMap[a] == undefined && orderMap[b] == undefined) {
38-
return a.localeCompare(b)
37+
// Sort a defined attribute.
38+
if (orderMap[a] !== undefined) {
39+
// With another defined attribute.
40+
if (orderMap[b] !== undefined) {
41+
return orderMap[a] - orderMap[b]
42+
}
43+
// With a data-* attribute or a regular attribute.
44+
return -1
45+
}
46+
47+
// Sort a data-* attribute.
48+
if (a.startsWith('data-')) {
49+
// With another data-* attribute.
50+
if (b.startsWith('data-')) {
51+
return a.localeCompare(b)
52+
}
53+
// With a defined attribute or a regular attribute.
54+
return 1
3955
}
40-
if (orderMap[a] == undefined) {
56+
57+
// Sort a regular attribute.
58+
// With a defined attribute.
59+
if (orderMap[b] !== undefined) {
4160
return 1
42-
} else if (orderMap[b] == undefined) {
61+
}
62+
// With a data-* attribute.
63+
if (b.startsWith('data-')) {
4364
return -1
4465
}
45-
return orderMap[a] - orderMap[b] || a.localeCompare(b)
66+
// With another regular attribute.
67+
return a.localeCompare(b)
4668
})
4769

4870
if (originalAttrs !== JSON.stringify(listOfAttributes)) {

test/rules/attr-sorted.spec.js

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const ruleOptions = {}
66
ruleOptions[ruleId] = true
77

88
describe(`Rules: ${ruleId}`, () => {
9-
it('Attribute unsorted tags must result in an error', () => {
9+
it('Unsorted defined attributes should throw error', () => {
1010
const code = '<div id="test" class="class" title="title"></div>'
1111

1212
const messages = HTMLHint.verify(code, ruleOptions)
@@ -16,45 +16,101 @@ describe(`Rules: ${ruleId}`, () => {
1616
expect(messages[0].message).toContain('["id","class","title"]')
1717
})
1818

19-
it('Attribute sorted tags that are unrecognizable should not throw error', () => {
20-
const code = '<div font="font" img="image" meta="meta"></div>'
19+
it('Sorted defined attributes should not throw error', () => {
20+
const code = '<div class="class" id="test" title="title"></div>'
2121

2222
const messages = HTMLHint.verify(code, ruleOptions)
2323

2424
expect(messages.length).toBe(0)
2525
})
2626

27-
it('Attribute unsorted tags that are unrecognizable should throw error', () => {
28-
const code = '<div img="image" meta="meta" font="font"></div>'
27+
it('Unsorted data-* attributes should throw error', () => {
28+
const code = '<main data-b="foo" data-a="bar"></main>'
2929

3030
const messages = HTMLHint.verify(code, ruleOptions)
3131

3232
expect(messages.length).toBe(1)
3333
expect(messages[0].rule.id).toBe(ruleId)
34-
expect(messages[0].message).toContain('["img","meta","font"]')
34+
expect(messages[0].message).toContain('["data-a","data-b"]')
3535
})
3636

37-
it('Attribute unsorted of tags of various types should throw error', () => {
38-
const code = '<div type="type" img="image" id="id" font="font"></div>'
37+
it('Sorted data-* attributes should not throw error', () => {
38+
const code = '<main data-a="bar" data-b="foo"></main>'
39+
40+
const messages = HTMLHint.verify(code, ruleOptions)
41+
42+
expect(messages.length).toBe(0)
43+
})
44+
45+
it('Unsorted regular attributes should throw error', () => {
46+
const code = '<button disabled dir="rtl">Click</button>'
47+
48+
const messages = HTMLHint.verify(code, ruleOptions)
49+
50+
expect(messages.length).toBe(1)
51+
expect(messages[0].rule.id).toBe(ruleId)
52+
expect(messages[0].message).toContain('["dir","disabled"]')
53+
})
54+
55+
it('Sorted regular attributes should not throw error', () => {
56+
const code = '<button dir="rtl" disabled>Click</button>'
57+
58+
const messages = HTMLHint.verify(code, ruleOptions)
59+
60+
expect(messages.length).toBe(0)
61+
})
62+
63+
it('Unsorted defined and data-* attributes should throw error', () => {
64+
const code = '<img data-focal="80" src="IMG.jpg" />'
65+
66+
const messages = HTMLHint.verify(code, ruleOptions)
67+
68+
expect(messages.length).toBe(1)
69+
expect(messages[0].rule.id).toBe(ruleId)
70+
expect(messages[0].message).toContain('["src","data-focal"]')
71+
})
72+
73+
it('Sorted defined and data-* attributes should not throw error', () => {
74+
const code = '<img src="IMG.jpg" data-focal="80" />'
75+
76+
const messages = HTMLHint.verify(code, ruleOptions)
77+
78+
expect(messages.length).toBe(0)
79+
})
80+
81+
it('Unsorted defined and regular attributes should throw error', () => {
82+
const code = '<input required value="foo" />'
3983

4084
const messages = HTMLHint.verify(code, ruleOptions)
4185

4286
expect(messages.length).toBe(1)
4387
expect(messages[0].rule.id).toBe(ruleId)
44-
expect(messages[0].message).toContain('["type","img","id","font"]')
88+
expect(messages[0].message).toContain('["value","required"]')
4589
})
4690

47-
it('link tag with rel before href should not throw error', () => {
48-
const code = '<link rel="stylesheet" href="https://example.com/style.css">'
91+
it('Sorted defined and regular attributes should not throw error', () => {
92+
const code = '<input value="foo" required />'
93+
4994
const messages = HTMLHint.verify(code, ruleOptions)
95+
5096
expect(messages.length).toBe(0)
5197
})
5298

53-
it('link tag with href before rel should throw error', () => {
54-
const code = '<link href="https://example.com/style.css" rel="stylesheet">'
99+
it('Unsorted data-* and regular attributes should throw error', () => {
100+
const code = '<section data-prop="abc" lang="en" />'
101+
55102
const messages = HTMLHint.verify(code, ruleOptions)
103+
56104
expect(messages.length).toBe(1)
57105
expect(messages[0].rule.id).toBe(ruleId)
58-
expect(messages[0].message).toContain('["href","rel"]')
106+
expect(messages[0].message).toContain('["lang","data-prop"]')
107+
})
108+
109+
it('Sorted data-* and regular attributes should not throw error', () => {
110+
const code = '<section lang="en" data-prop="abc" />'
111+
112+
const messages = HTMLHint.verify(code, ruleOptions)
113+
114+
expect(messages.length).toBe(0)
59115
})
60116
})

0 commit comments

Comments
 (0)