validate ref callees (#686)

pull/731/head
Rich Harris 7 years ago
parent 5c4905a595
commit c1f34e3e2a

@ -1,6 +1,8 @@
import * as namespaces from '../../utils/namespaces';
import validateElement from './validateElement';
import validateWindow from './validateWindow';
import fuzzymatch from '../utils/fuzzymatch'
import flattenReference from '../../utils/flattenReference';
import { Validator } from '../index';
import { Node } from '../../interfaces';
@ -11,6 +13,9 @@ const meta = new Map([[':Window', validateWindow]]);
export default function validateHtml(validator: Validator, html: Node) {
let elementDepth = 0;
const refs = new Map();
const refCallees: Node[] = [];
function visit(node: Node) {
if (node.type === 'Element') {
if (
@ -25,12 +30,12 @@ export default function validateHtml(validator: Validator, html: Node) {
}
if (meta.has(node.name)) {
return meta.get(node.name)(validator, node);
return meta.get(node.name)(validator, node, refs, refCallees);
}
elementDepth += 1;
validateElement(validator, node);
validateElement(validator, node, refs, refCallees);
} else if (node.type === 'EachBlock') {
if (validator.helpers.has(node.context)) {
let c = node.expression.end;
@ -61,4 +66,20 @@ export default function validateHtml(validator: Validator, html: Node) {
}
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);
}
});
}

@ -2,7 +2,7 @@ import validateEventHandler from './validateEventHandler';
import { Validator } from '../index';
import { Node } from '../../interfaces';
export default function validateElement(validator: Validator, node: Node) {
export default function validateElement(validator: Validator, node: Node, refs: Map<string, Node[]>, refCallees: Node[]) {
const isComponent =
node.name === ':Self' || validator.components.has(node.name);
@ -16,6 +16,11 @@ export default function validateElement(validator: Validator, node: Node) {
let hasTransition: boolean;
node.attributes.forEach((attribute: Node) => {
if (attribute.type === 'Ref') {
if (!refs.has(attribute.name)) refs.set(attribute.name, []);
refs.get(attribute.name).push(node);
}
if (!isComponent && attribute.type === 'Binding') {
const { name } = attribute;
@ -80,7 +85,7 @@ export default function validateElement(validator: Validator, node: Node) {
);
}
} else if (attribute.type === 'EventHandler') {
validateEventHandler(validator, attribute);
validateEventHandler(validator, attribute, refCallees);
} else if (attribute.type === 'Transition') {
const bidi = attribute.intro && attribute.outro;

@ -7,7 +7,8 @@ const validBuiltins = new Set(['set', 'fire', 'destroy']);
export default function validateEventHandlerCallee(
validator: Validator,
attribute: Node
attribute: Node,
refCallees: Node[]
) {
const { callee, start, type } = attribute.expression;
@ -18,6 +19,12 @@ export default function validateEventHandlerCallee(
const { name } = flattenReference(callee);
if (name === 'this' || name === 'event') return;
if (name === 'refs') {
refCallees.push(callee);
return;
}
if (
(callee.type === 'Identifier' && validBuiltins.has(callee.name)) ||
validator.methods.has(callee.name)

@ -14,7 +14,7 @@ const validBindings = [
'scrollY',
];
export default function validateWindow(validator: Validator, node: Node) {
export default function validateWindow(validator: Validator, node: Node, refs: Map<string, Node[]>, refCallees: Node[]) {
node.attributes.forEach((attribute: Node) => {
if (attribute.type === 'Binding') {
if (attribute.value.type !== 'Identifier') {
@ -50,7 +50,7 @@ export default function validateWindow(validator: Validator, node: Node) {
}
}
} else if (attribute.type === 'EventHandler') {
validateEventHandler(validator, attribute);
validateEventHandler(validator, attribute, refCallees);
}
});
}

@ -17,6 +17,10 @@ describe("validate", () => {
const filename = `test/validator/samples/${dir}/input.html`;
const input = fs.readFileSync(filename, "utf-8").replace(/\s+$/, "");
const expectedWarnings = tryToLoadJson(`test/validator/samples/${dir}/warnings.json`) || [];
const expectedErrors = tryToLoadJson(`test/validator/samples/${dir}/errors.json`);
let error;
try {
const warnings = [];
@ -30,20 +34,25 @@ describe("validate", () => {
}
});
const expectedWarnings =
tryToLoadJson(`test/validator/samples/${dir}/warnings.json`) || [];
assert.deepEqual(warnings, expectedWarnings);
} catch (err) {
try {
const expected = require(`./samples/${dir}/errors.json`)[0];
} catch (e) {
error = e;
}
const expected = expectedErrors && expectedErrors[0];
assert.equal(err.message, expected.message);
assert.deepEqual(err.loc, expected.loc);
assert.equal(err.pos, expected.pos);
} catch (err2) {
throw err2.code === "MODULE_NOT_FOUND" ? err : err2;
if (error || expected) {
if (error && !expected) {
throw error;
}
if (expected && !error) {
throw new Error(`Expected an error: ${expected.message}`);
}
assert.equal(error.message, expected.message);
assert.deepEqual(error.loc, expected.loc);
assert.equal(error.pos, expected.pos);
}
});
});

@ -0,0 +1,8 @@
[{
"message": "'refs.inputx' does not exist (did you mean 'refs.input'?)",
"pos": 36,
"loc": {
"line": 2,
"column": 18
}
}]

@ -0,0 +1,2 @@
<input ref:input>
<button on:click='refs.inputx.focus()'>focus input</button>

@ -0,0 +1,2 @@
<input ref:input>
<button on:click='refs.input.focus()'>focus input</button>
Loading…
Cancel
Save