You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte/src/validate/html/index.ts

110 lines
2.6 KiB

import validateElement from './validateElement';
import validateWindow from './validateWindow';
import validateHead from './validateHead';
import a11y from './a11y';
import fuzzymatch from '../utils/fuzzymatch'
import flattenReference from '../../utils/flattenReference';
import { Validator } from '../index';
import { Node } from '../../interfaces';
function isEmptyBlock(node: Node) {
if (!/Block$/.test(node.type) || !node.children) return false;
if (node.children.length > 1) return false;
const child = node.children[0];
return !child || (child.type === 'Text' && !/\S/.test(child.data));
}
export default function validateHtml(validator: Validator, html: Node) {
const refs = new Map();
const refCallees: Node[] = [];
const stack: Node[] = [];
const elementStack: Node[] = [];
function visit(node: Node) {
if (node.type === 'Window') {
validateWindow(validator, node, refs, refCallees);
}
else if (node.type === 'Head') {
validateHead(validator, node, refs, refCallees);
}
else if (node.type === 'Element') {
const isComponent =
node.name === ':Self' ||
node.name === ':Component' ||
validator.components.has(node.name);
validateElement(
validator,
node,
refs,
refCallees,
stack,
elementStack,
isComponent
);
if (!isComponent) {
a11y(validator, node, elementStack);
}
}
else if (node.type === 'EachBlock') {
if (validator.helpers.has(node.context)) {
let c = node.expression.end;
// find start of context
while (/\s/.test(validator.source[c])) c += 1;
c += 2;
while (/\s/.test(validator.source[c])) c += 1;
validator.warn(
`Context clashes with a helper. Rename one or the other to eliminate any ambiguity`,
c
);
}
}
if (validator.options.dev && isEmptyBlock(node)) {
validator.warn('Empty block', node.start);
}
if (node.children) {
if (node.type === 'Element') elementStack.push(node);
stack.push(node);
node.children.forEach(visit);
stack.pop();
if (node.type === 'Element') elementStack.pop();
}
if (node.else) {
visit(node.else);
}
if (node.type === 'AwaitBlock') {
visit(node.pending);
visit(node.then);
visit(node.catch);
}
}
html.children.forEach(visit);
refCallees.forEach(callee => {
const { parts } = flattenReference(callee);
const ref = parts[1];
if (refs.has(ref)) {
// TODO check method is valid, e.g. `audio.stop()` should be `audio.pause()`
} else {
const match = fuzzymatch(ref, Array.from(refs.keys()));
let message = `'refs.${ref}' does not exist`;
if (match) message += ` (did you mean 'refs.${match}'?)`;
validator.error(message, callee.start);
}
});
}