capsule-lint
Version:
The official Capsule linting package.
82 lines (68 loc) • 2.62 kB
text/typescript
import { Block } from 'htmlhint/htmlparser';
import { Rule } from 'htmlhint/types';
const rule: Rule = {
id: 'tag-pair',
description: 'Tag must be paired.',
init(parser, reporter) {
const stack: Block[] = [];
const mapEmptyTags = parser.makeMap(
'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,track,command,source,keygen,wbr'
);
parser.addListener('tagstart', (event) => {
const tagName = event.tagName.toLowerCase();
if (mapEmptyTags[tagName] === undefined && !event.close) {
stack.push({ tagName, ...event });
}
});
parser.addListener('tagend', (event) => {
const tagName = event.tagName.toLowerCase();
if(tagName.startsWith('#')) {
return;
}
let pos;
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos].tagName === tagName) {
break;
}
}
if (pos >= 0) {
const arrTags = [];
for (let i = stack.length - 1; i > pos; i--) {
arrTags.push(`</${stack[i].tagName}>`);
}
if (arrTags.length > 0) {
const lastEvent = stack[stack.length - 1];
reporter.error(
`Tag must be paired, missing: [ ${arrTags.join('')} ], start tag match failed [ ${lastEvent.raw} ] on line ${lastEvent.line}.`,
lastEvent.line,
lastEvent.col,
this,
lastEvent.raw
);
}
stack.length = pos;
}
else {
reporter.error(
`Tag must be paired, no start tag: [ ${event.raw} ]`,
event.line,
event.col,
this,
event.raw
);
}
});
parser.addListener('end', (event) => {
for (let i = stack.length - 1; i >= 0; i--) {
reporter.error(
`Tag must be paired, missing: [ </${stack[i].tagName}> ], start tag match failed [ ${stack[i].raw} ] on line ${stack[i].line}.`,
stack[i].line,
stack[i].col,
this,
stack[i].raw
);
}
});
},
};
export default rule;