mirror of https://github.com/sveltejs/svelte
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.
157 lines
4.2 KiB
157 lines
4.2 KiB
import Node from './shared/Node';
|
|
import Expression from './shared/Expression';
|
|
import addToSet from '../../utils/addToSet';
|
|
import flattenReference from '../../utils/flattenReference';
|
|
import validCalleeObjects from '../../utils/validCalleeObjects';
|
|
import list from '../../utils/list';
|
|
|
|
const validBuiltins = new Set(['set', 'fire', 'destroy']);
|
|
|
|
export default class EventHandler extends Node {
|
|
name: string;
|
|
dependencies: Set<string>;
|
|
expression: Node;
|
|
callee: any; // TODO
|
|
|
|
usesComponent: boolean;
|
|
usesContext: boolean;
|
|
isCustomEvent: boolean;
|
|
shouldHoist: boolean;
|
|
|
|
insertionPoint: number;
|
|
args: Expression[];
|
|
snippet: string;
|
|
|
|
constructor(component, parent, scope, info) {
|
|
super(component, parent, scope, info);
|
|
|
|
this.name = info.name;
|
|
component.used.events.add(this.name);
|
|
|
|
this.dependencies = new Set();
|
|
|
|
if (info.expression) {
|
|
this.validateExpression(info.expression);
|
|
|
|
this.callee = flattenReference(info.expression.callee);
|
|
|
|
this.insertionPoint = info.expression.start;
|
|
|
|
this.usesComponent = !validCalleeObjects.has(this.callee.name);
|
|
this.usesContext = false;
|
|
|
|
this.args = info.expression.arguments.map(param => {
|
|
const expression = new Expression(component, this, scope, param);
|
|
addToSet(this.dependencies, expression.dependencies);
|
|
if (expression.usesContext) this.usesContext = true;
|
|
return expression;
|
|
});
|
|
|
|
this.snippet = `[✂${info.expression.start}-${info.expression.end}✂];`;
|
|
} else {
|
|
this.callee = null;
|
|
this.insertionPoint = null;
|
|
|
|
this.args = null;
|
|
this.usesComponent = true;
|
|
this.usesContext = false;
|
|
|
|
this.snippet = null; // TODO handle shorthand events here?
|
|
}
|
|
|
|
this.isCustomEvent = component.events.has(this.name);
|
|
this.shouldHoist = !this.isCustomEvent && parent.hasAncestor('EachBlock');
|
|
}
|
|
|
|
render(component, block, context, hoisted) { // TODO hoist more event handlers
|
|
if (this.insertionPoint === null) return; // TODO handle shorthand events here?
|
|
|
|
if (!validCalleeObjects.has(this.callee.name)) {
|
|
const component_name = hoisted ? `component` : block.alias(`component`);
|
|
|
|
// allow event.stopPropagation(), this.select() etc
|
|
// TODO verify that it's a valid callee (i.e. built-in or declared method)
|
|
if (this.callee.name[0] === '$' && !component.methods.has(this.callee.name)) {
|
|
component.code.overwrite(
|
|
this.insertionPoint,
|
|
this.insertionPoint + 1,
|
|
`${component_name}.store.`
|
|
);
|
|
} else {
|
|
component.code.prependRight(
|
|
this.insertionPoint,
|
|
`${component_name}.`
|
|
);
|
|
}
|
|
}
|
|
|
|
if (this.isCustomEvent) {
|
|
this.args.forEach(arg => {
|
|
arg.overwriteThis(context);
|
|
});
|
|
|
|
if (this.callee && this.callee.name === 'this') {
|
|
const node = this.callee.nodes[0];
|
|
component.code.overwrite(node.start, node.end, context, {
|
|
storeName: true,
|
|
contentOnly: true
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
validateExpression(expression) {
|
|
const { callee, type } = expression;
|
|
|
|
if (type !== 'CallExpression') {
|
|
this.component.error(expression, {
|
|
code: `invalid-event-handler`,
|
|
message: `Expected a call expression`
|
|
});
|
|
}
|
|
|
|
const { component } = this;
|
|
const { name } = flattenReference(callee);
|
|
|
|
if (validCalleeObjects.has(name) || name === 'options') return;
|
|
|
|
if (name === 'refs') {
|
|
this.component.refCallees.push(callee);
|
|
return;
|
|
}
|
|
|
|
if (
|
|
(callee.type === 'Identifier' && validBuiltins.has(name)) ||
|
|
this.component.methods.has(name)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (name[0] === '$') {
|
|
// assume it's a store method
|
|
return;
|
|
}
|
|
|
|
const validCallees = ['this.*', 'refs.*', 'event.*', 'options.*', 'console.*'].concat(
|
|
Array.from(validBuiltins),
|
|
Array.from(this.component.methods.keys())
|
|
);
|
|
|
|
let message = `'${component.source.slice(callee.start, callee.end)}' is an invalid callee ` ;
|
|
|
|
if (name === 'store') {
|
|
message += `(did you mean '$${component.source.slice(callee.start + 6, callee.end)}(...)'?)`;
|
|
} else {
|
|
message += `(should be one of ${list(validCallees)})`;
|
|
|
|
if (callee.type === 'Identifier' && component.helpers.has(callee.name)) {
|
|
message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;
|
|
}
|
|
}
|
|
|
|
component.warn(expression, {
|
|
code: `invalid-callee`,
|
|
message
|
|
});
|
|
}
|
|
} |