Skip to content

Commit

Permalink
Merge pull request #731 from sveltejs/gh-686
Browse files Browse the repository at this point in the history
validate ref callees
  • Loading branch information
Rich-Harris committed Jul 29, 2017
2 parents 57e7f75 + c1f34e3 commit 6a74db0
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 18 deletions.
25 changes: 23 additions & 2 deletions src/validate/html/index.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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 (
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
});
}
9 changes: 7 additions & 2 deletions src/validate/html/validateElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;

Expand Down Expand Up @@ -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;

Expand Down
9 changes: 8 additions & 1 deletion src/validate/html/validateEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/validate/html/validateWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -50,7 +50,7 @@ export default function validateWindow(validator: Validator, node: Node) {
}
}
} else if (attribute.type === 'EventHandler') {
validateEventHandler(validator, attribute);
validateEventHandler(validator, attribute, refCallees);
}
});
}
31 changes: 20 additions & 11 deletions test/validator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];

Expand All @@ -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);
}
});
});
Expand Down
8 changes: 8 additions & 0 deletions test/validator/samples/event-handler-ref-invalid/errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[{
"message": "'refs.inputx' does not exist (did you mean 'refs.input'?)",
"pos": 36,
"loc": {
"line": 2,
"column": 18
}
}]
2 changes: 2 additions & 0 deletions test/validator/samples/event-handler-ref-invalid/input.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<input ref:input>
<button on:click='refs.inputx.focus()'>focus input</button>
2 changes: 2 additions & 0 deletions test/validator/samples/event-handler-ref/input.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<input ref:input>
<button on:click='refs.input.focus()'>focus input</button>
1 change: 1 addition & 0 deletions test/validator/samples/event-handler-ref/warnings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]

0 comments on commit 6a74db0

Please sign in to comment.