All files / src/rules input-missing-label.ts

100% Statements 20/20
100% Branches 10/10
100% Functions 5/5
100% Lines 18/18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46        1x           3x 3x 3x     3x 3x 1x       2x 1x     1x           3x 2x       2x 2x 2x 1x   1x   1x    
import { DOMNode, DOMTree } from 'dom';
import { Rule, RuleReport, RuleParserProxy } from '../rule';
import { DOMReadyEvent } from '../event';
 
export = {
	name: 'input-missing-label',
	init,
} as Rule;
 
function init(parser: RuleParserProxy){
	parser.on('dom:ready', (event: DOMReadyEvent, report: RuleReport) => {
		const root = event.document;
		for (const elem of root.getElementsByTagName('input')){
 
			/* try to find label by id */
			const id = elem.getAttribute('id');
			if (findLabelById(root, id)){
				continue;
			}
 
			/* try to find parent label (input nested in label) */
			if (findLabelByParent(elem)){
				continue;
			}
 
			report(elem, 'Input element does not have a label');
		}
	});
}
 
function findLabelById(root: DOMTree, id: string): DOMNode {
	if (!id) return null;
	return root.find((node: DOMNode) => node.is('label') && node.getAttribute('for') === id);
}
 
function findLabelByParent(el: DOMNode): DOMNode {
	let cur = el.parent;
	while (cur){
		if (cur.is('label')){
			return cur;
		}
		cur = cur.parent;
	}
	return null;
}