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/compiler/compile/nodes/Binding.ts

100 lines
3.0 KiB

import Node from './shared/Node';
import get_object from '../utils/get_object';
import Expression from './shared/Expression';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import {dimensions} from '../../utils/patterns';
import { Node as ESTreeNode } from 'estree';
import { TemplateNode } from '../../interfaces';
import Element from './Element';
import InlineComponent from './InlineComponent';
import Window from './Window';
import { clone } from '../../utils/clone';
import compiler_errors from '../compiler_errors';
// TODO this should live in a specific binding
const read_only_media_attributes = new Set([
'duration',
'buffered',
'seekable',
'played',
'seeking',
'ended',
'videoHeight',
'videoWidth'
]);
export default class Binding extends Node {
type: 'Binding';
name: string;
expression: Expression;
raw_expression: ESTreeNode; // TODO exists only for bind:this — is there a more elegant solution?
is_contextual: boolean;
is_readonly: boolean;
constructor(component: Component, parent: Element | InlineComponent | Window, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info);
if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') {
component.error(info, compiler_errors.invalid_directive_value);
return;
}
this.name = info.name;
this.expression = new Expression(component, this, scope, info.expression);
this.raw_expression = clone(info.expression);
const { name } = get_object(this.expression.node);
this.is_contextual = Array.from(this.expression.references).some(name => scope.names.has(name));
// make sure we track this as a mutable ref
if (scope.is_let(name)) {
component.error(this, compiler_errors.invalid_binding_let);
return;
} else if (scope.names.has(name)) {
if (scope.is_await(name)) {
component.error(this, compiler_errors.invalid_binding_await);
return;
}
scope.dependencies_for_name.get(name).forEach(name => {
const variable = component.var_lookup.get(name);
if (variable) {
variable.mutated = true;
}
});
} else {
const variable = component.var_lookup.get(name);
if (!variable || variable.global) {
component.error(this.expression.node as any, compiler_errors.binding_undeclared(name));
return;
}
variable[this.expression.node.type === 'MemberExpression' ? 'mutated' : 'reassigned'] = true;
if (info.expression.type === 'Identifier' && !variable.writable) {
component.error(this.expression.node as any, compiler_errors.invalid_binding_writibale);
return;
}
}
const type = parent.get_static_attribute_value('type');
this.is_readonly =
dimensions.test(this.name) ||
(isElement(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file')) /* TODO others? */);
}
is_readonly_media_attribute() {
return read_only_media_attributes.has(this.name);
}
}
function isElement(node: Node): node is Element {
return !!(node as any).is_media_node;
}