All files / src/rules void.ts

96.67% Statements 29/30
97.06% Branches 33/34
100% Functions 6/6
96.67% Lines 29/30
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73      1x   1x                   1x 1x 1x 1x       418x 418x   418x 874x 874x   874x 871x     874x 871x         871x 13x         871x   871x 52x 2x     52x 1x       871x 1x             418x 406x 10x 2x        
import { DOMNode } from '../dom';
import { Rule, RuleReport, RuleParserProxy } from '../rule';
import { TagCloseEvent } from '../event';
import { NodeClosed } from '../dom';
 
export = {
	name: 'void',
	init,
 
	defaults: {
		style: 'omit',
	},
 
} as Rule;
 
enum Style {
	Any = 0,
	AlwaysOmit = 1,
	AlwaysSelfclose = 2,
}
 
function init(parser: RuleParserProxy, userOptions: any){
	const options = Object.assign({}, this.defaults, userOptions);
	const style = parseStyle(options.style);
 
	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)
 
		if (current && current.meta){
			validateCurrent(current, report);
		}
 
		if (active && active.meta){
			validateActive(active, report);
		}
	});
 
	function validateCurrent(node: DOMNode, report: RuleReport): void {
		if (node.voidElement && node.closed === NodeClosed.EndTag){
			report(node, `End tag for <${node.tagName}> must be omitted`);
		}
	}
 
	function validateActive(node: DOMNode, report: RuleReport): void {
		const selfOrOmitted = node.closed === NodeClosed.VoidOmitted || node.closed === NodeClosed.VoidSelfClosed;
 
		if (node.voidElement){
			if (style === Style.AlwaysOmit && node.closed === NodeClosed.VoidSelfClosed){
				report(node, `Expected omitted end tag <${node.tagName}> instead of self-closing element <${node.tagName}/>`);
			}
 
			if (style === Style.AlwaysSelfclose && node.closed === NodeClosed.VoidOmitted){
				report(node, `Expected self-closing element <${node.tagName}/> instead of omitted end-tag <${node.tagName}>`);
			}
		}
 
		if (selfOrOmitted && node.voidElement === false){
			report(node, `End tag for <${node.tagName}> must not be omitted`);
		}
 
	}
}
 
function parseStyle(name: string): Style {
	switch (name){
	case 'any': return Style.Any;
	case 'omit': return Style.AlwaysOmit;
	case 'selfclose': return Style.AlwaysSelfclose;
	default: return Style.Any;
	}
}