Skip to content

Commit 5bd40fb

Browse files
olorethedaviddias
authored andcommittedMay 18, 2020
feat: new rule: input-requires-label - All inputs require a label (#159)
1 parent 8ad7cff commit 5bd40fb

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed
 

‎src/rules/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export { default as idClassValue } from './id-class-value';
1616
export { default as idUnique } from './id-unique';
1717
export { default as inlineScriptDisabled } from './inline-script-disabled';
1818
export { default as inlineStyleDisabled } from './inline-style-disabled';
19+
export { default as inputRequiresLabel } from './input-requires-label';
1920
export { default as scriptDisabled } from './script-disabled';
2021
export { default as spaceTabMixedDisabled } from './space-tab-mixed-disabled';
2122
export { default as specCharEscape } from './spec-char-escape';

‎src/rules/input-requires-label.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export default {
2+
id: 'input-requires-label',
3+
description: 'All [ input ] tags must have a corresponding [ label ] tag. ',
4+
init: function(parser, reporter){
5+
var self = this,
6+
labelTags = [],
7+
inputTags = [];
8+
9+
parser.addListener('tagstart', function(event) {
10+
var tagName = event.tagName.toLowerCase(),
11+
mapAttrs = parser.getMapAttrs(event.attrs),
12+
col = event.col + tagName.length + 1;
13+
14+
if (tagName === 'input') {
15+
inputTags.push({event: event, col: col, id: mapAttrs['id']});
16+
}
17+
18+
if (tagName === 'label') {
19+
if (('for' in mapAttrs) && mapAttrs['for'] !== '') {
20+
labelTags.push({event: event, col: col, forValue: mapAttrs['for']});
21+
}
22+
}
23+
24+
});
25+
26+
parser.addListener('end', function() {
27+
inputTags.forEach(function(inputTag) {
28+
if (!hasMatchingLabelTag(inputTag)) {
29+
reporter.warn('No matching [ label ] tag found.', inputTag.event.line, inputTag.col, self, inputTag.event.raw);
30+
}
31+
});
32+
});
33+
34+
35+
function hasMatchingLabelTag(inputTag) {
36+
var found = false;
37+
labelTags.forEach(function(labelTag){
38+
if (inputTag.id && (inputTag.id === labelTag.forValue)) {
39+
found = true;
40+
}
41+
});
42+
return found;
43+
44+
}
45+
}
46+
};
+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const expect = require("expect.js");
2+
3+
const HTMLHint = require('../../dist/htmlhint.js').HTMLHint;
4+
5+
const ruleId = 'input-requires-label';
6+
const ruleOptions = {};
7+
8+
ruleOptions[ruleId] = true;
9+
10+
describe(`Rules: ${ruleId}`, function(){
11+
12+
describe('Successful cases', function() {
13+
14+
it('Input tag with a matching label before should result in no error', function () {
15+
var code = '<label for="some-id"/><input id="some-id" type="password" />';
16+
var messages = HTMLHint.verify(code, ruleOptions);
17+
expect(messages.length).to.be(0);
18+
});
19+
20+
it('Input tag with a matching label after should result in no error', function () {
21+
var code = '<input id="some-id" type="password" /> <label for="some-id"/>';
22+
var messages = HTMLHint.verify(code, ruleOptions);
23+
expect(messages.length).to.be(0);
24+
});
25+
});
26+
27+
28+
describe('Error cases', function() {
29+
30+
it('Input tag with no matching label should result in an error', function () {
31+
var code = '<input type="password">';
32+
var messages = HTMLHint.verify(code, ruleOptions);
33+
expect(messages.length).to.be(1);
34+
expect(messages[0].rule.id).to.be(ruleId);
35+
expect(messages[0].line).to.be(1);
36+
expect(messages[0].col).to.be(7);
37+
expect(messages[0].type).to.be('warning');
38+
});
39+
40+
it('Input tag with label that doesn\'t match id should result in error', function () {
41+
var code = '<input id="some-id" type="password" /> <label for="some-other-id"/>';
42+
var messages = HTMLHint.verify(code, ruleOptions);
43+
expect(messages.length).to.be(1);
44+
expect(messages[0].rule.id).to.be(ruleId);
45+
expect(messages[0].line).to.be(1);
46+
expect(messages[0].col).to.be(7);
47+
expect(messages[0].type).to.be('warning');
48+
});
49+
50+
it('Input tag with blank label:for should result in error', function () {
51+
var code = '<input id="some-id" type="password" /> <label for=""/>';
52+
var messages = HTMLHint.verify(code, ruleOptions);
53+
expect(messages.length).to.be(1);
54+
expect(messages[0].rule.id).to.be(ruleId);
55+
expect(messages[0].line).to.be(1);
56+
expect(messages[0].col).to.be(7);
57+
expect(messages[0].type).to.be('warning');
58+
});
59+
60+
it('Input tag with no id should result in error', function () {
61+
var code = '<input type="password" /> <label for="something"/>';
62+
var messages = HTMLHint.verify(code, ruleOptions);
63+
expect(messages.length).to.be(1);
64+
expect(messages[0].rule.id).to.be(ruleId);
65+
expect(messages[0].line).to.be(1);
66+
expect(messages[0].col).to.be(7);
67+
expect(messages[0].type).to.be('warning');
68+
});
69+
});
70+
71+
});

0 commit comments

Comments
 (0)
Please sign in to comment.