capsule-lint
Advanced tools
Comparing version 0.4.15 to 0.4.16
{ | ||
"name": "capsule-lint", | ||
"version": "0.4.15", | ||
"version": "0.4.16", | ||
"description": "The official Capsule linting package.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -0,1 +1,2 @@ | ||
import { Reporter } from 'htmlhint'; | ||
import { Block } from 'htmlhint/htmlparser'; | ||
@@ -5,2 +6,79 @@ import { Rule } from 'htmlhint/types'; | ||
function isMatchingTag(tagName: string, tag: string) { | ||
return !!tag.match(new RegExp(`^<${tagName}`)); | ||
} | ||
type Tag = { | ||
open: boolean | ||
raw: string; | ||
tagName: string | ||
event: Block; | ||
}; | ||
class BlockTag { | ||
parent?: BlockTag; | ||
openTag?: Tag; | ||
closeTag?: Tag; | ||
children: BlockTag[]; | ||
constructor(tag?: Tag, parent?: BlockTag) { | ||
if(tag && tag.open) { | ||
this.openTag = tag; | ||
} | ||
else if(tag && !tag.open) { | ||
this.closeTag = tag; | ||
} | ||
this.parent = parent; | ||
this.children = []; | ||
} | ||
} | ||
function createTree(stack: Tag[]) { | ||
const root: BlockTag = new BlockTag(); | ||
let currentTag: BlockTag = root; | ||
for(const tag of stack) { | ||
if(tag.open) { | ||
currentTag.children.push( | ||
currentTag = new BlockTag(tag, currentTag) | ||
); | ||
} | ||
else if(!tag.open && currentTag.openTag?.tagName === tag.tagName) { | ||
currentTag.closeTag = tag; | ||
currentTag = currentTag.parent | ||
} | ||
else { | ||
currentTag.children.push(new BlockTag(tag, currentTag.parent ?? root)); | ||
} | ||
} | ||
return root; | ||
} | ||
function lintTree(nodes: BlockTag[], reporter: Reporter, rule: Rule) { | ||
for(const node of nodes) { | ||
if(node.children) { | ||
lintTree(node.children, reporter, rule) | ||
} | ||
if(node.openTag && node.closeTag) { | ||
continue; | ||
} | ||
else if(node.openTag) { | ||
reporter.error( | ||
`Tag [${node.openTag.raw}] is missing a closing tag: [</${node.openTag.tagName}>]`, | ||
node.openTag.event.line, | ||
node.openTag.event.col, | ||
rule, | ||
node.openTag.raw | ||
); | ||
} | ||
else if(node.closeTag) { | ||
reporter.error(`Tag must be paired, no start tag: [ ${node.closeTag.raw} ]`, node.closeTag.event.line, node.closeTag.event.col, rule, node.closeTag.event.raw); | ||
} | ||
} | ||
} | ||
const rule: Rule = { | ||
@@ -10,15 +88,19 @@ id: 'freemarker-tags', | ||
init(parser, reporter) { | ||
const stack: { | ||
tag: string, | ||
event: Block | ||
}[] = []; | ||
const stack: Tag[] = []; | ||
const blockTags = ['#if', '#list']; | ||
const pattern = new RegExp(`^<(${blockTags.join('|')})`) | ||
parser.addListener('text', (event) => { | ||
try { | ||
for(const tag of parse(event.raw)) { | ||
if(tag.match(pattern)) { | ||
stack.push({ tag, event }); | ||
const match = tag.match(pattern); | ||
if(match) { | ||
stack.push({ | ||
event, | ||
raw: tag, | ||
tagName: match[1], | ||
open: true | ||
}); | ||
} | ||
@@ -45,13 +127,12 @@ } | ||
if(!stack.length) { | ||
reporter.error(`Conditional must be paired, no start tag: [ ${event.raw} ]`, event.line, event.col, this, event.raw); | ||
} | ||
stack.pop(); | ||
stack.push({ | ||
raw: event.raw, | ||
tagName: event.tagName, | ||
event, | ||
open: false | ||
}); | ||
}); | ||
parser.addListener('end', () => { | ||
for(const { tag, event } of stack) { | ||
reporter.error(`Tag [${tag}] is missing a closing tag: [</${tag.match(pattern)[1]}>]`, event.line, event.col, this, event.raw) | ||
} | ||
lintTree(createTree(stack).children, reporter, this); | ||
}) | ||
@@ -58,0 +139,0 @@ }, |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1024306
7949