All files / src/rules close-order.ts

100% Statements 14/14
100% Branches 10/10
100% Functions 2/2
100% Lines 14/14
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      1x           11x 16x 16x     16x 1x 1x             15x 4x       11x 1x 1x       10x 1x        
import { Rule, RuleReport, RuleParserProxy } from '../rule';
import { TagCloseEvent } from '../event';
 
export = {
	name: 'close-order',
	init,
} as Rule;
 
function init(parser: RuleParserProxy){
	parser.on('tag:close', (event: TagCloseEvent, report: RuleReport) => {
		const current = event.target;     // The current element being closed
		const active = event.previous;    // The current active element (that is, the current element on the stack)
 
		/* handle unclosed tags */
		if (!current){
			report(event.previous, `Missing close-tag, expected '</${active.tagName}>' but document ended before it was found.`);
			return;
		}
 
		/* void elements are always closed in correct order but if the markup contains
		 * an end-tag for it it should be ignored here since the void element is
		 * implicitly closed in the right order, so the current active element is the
		 * parent. */
		if (current.voidElement){
			return;
		}
 
		/* handle unopened tags */
		if (!active || active.isRootElement()){
			report(event.previous, "Unexpected close-tag, expected opening tag.");
			return;
		}
 
		/* check for matching tagnames */
		if (current.tagName !== active.tagName){
			report(event.target, `Mismatched close-tag, expected '</${active.tagName}>' but found '</${current.tagName}>'.`, current.location);
		}
	});
}