mirror of https://github.com/sveltejs/svelte
parent
85b731c1bc
commit
f45e2b70fd
@ -0,0 +1,7 @@
|
||||
export {
|
||||
onMount,
|
||||
onDestroy,
|
||||
beforeUpdate,
|
||||
afterUpdate,
|
||||
createEventDispatcher
|
||||
} from './internal.js';
|
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
export default {
|
||||
input: 'store.js',
|
||||
output: {
|
||||
file: 'store.umd.js',
|
||||
format: 'umd',
|
||||
name: 'svelte',
|
||||
extend: true
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,27 +1,24 @@
|
||||
import Node from './shared/Node';
|
||||
import Expression from './shared/Expression';
|
||||
import Component from '../Component';
|
||||
|
||||
export default class Action extends Node {
|
||||
type: 'Action';
|
||||
name: string;
|
||||
expression: Expression;
|
||||
usesContext: boolean;
|
||||
|
||||
constructor(component, parent, scope, info) {
|
||||
constructor(component: Component, parent, scope, info) {
|
||||
super(component, parent, scope, info);
|
||||
|
||||
this.name = info.name;
|
||||
|
||||
component.used.actions.add(this.name);
|
||||
component.warn_if_undefined(info, scope);
|
||||
|
||||
if (!component.actions.has(this.name)) {
|
||||
component.error(this, {
|
||||
code: `missing-action`,
|
||||
message: `Missing action '${this.name}'`
|
||||
});
|
||||
}
|
||||
this.name = info.name;
|
||||
|
||||
this.expression = info.expression
|
||||
? new Expression(component, this, scope, info.expression)
|
||||
: null;
|
||||
|
||||
this.usesContext = this.expression && this.expression.usesContext;
|
||||
}
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
import Node from './shared/Node';
|
||||
import Block from '../render-dom/Block';
|
||||
import mapChildren from './shared/mapChildren';
|
||||
import TemplateScope from './shared/TemplateScope';
|
||||
|
||||
export default class CatchBlock extends Node {
|
||||
block: Block;
|
||||
scope: TemplateScope;
|
||||
children: Node[];
|
||||
|
||||
constructor(component, parent, scope, info) {
|
||||
super(component, parent, scope, info);
|
||||
this.children = mapChildren(component, parent, scope, info.children);
|
||||
|
||||
this.scope = scope.child();
|
||||
this.scope.add(parent.error, parent.expression.dependencies);
|
||||
this.children = mapChildren(component, parent, this.scope, info.children);
|
||||
|
||||
this.warnIfEmptyBlock();
|
||||
}
|
||||
|
@ -1,164 +1,42 @@
|
||||
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']);
|
||||
import Component from '../Component';
|
||||
import deindent from '../../utils/deindent';
|
||||
|
||||
export default class EventHandler extends Node {
|
||||
name: string;
|
||||
modifiers: Set<string>;
|
||||
dependencies: Set<string>;
|
||||
expression: Node;
|
||||
callee: any; // TODO
|
||||
|
||||
usesComponent: boolean;
|
||||
expression: Expression;
|
||||
handler_name: string;
|
||||
usesContext: boolean;
|
||||
usesEventObject: boolean;
|
||||
isCustomEvent: boolean;
|
||||
shouldHoist: boolean;
|
||||
|
||||
insertionPoint: number;
|
||||
args: Expression[];
|
||||
snippet: string;
|
||||
|
||||
constructor(component, parent, scope, info) {
|
||||
super(component, parent, scope, info);
|
||||
constructor(component: Component, parent, template_scope, info) {
|
||||
super(component, parent, template_scope, info);
|
||||
|
||||
this.name = info.name;
|
||||
this.modifiers = new Set(info.modifiers);
|
||||
|
||||
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.usesEventObject = this.callee.name === 'event';
|
||||
|
||||
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;
|
||||
if (expression.usesEvent) this.usesEventObject = true;
|
||||
return expression;
|
||||
});
|
||||
|
||||
this.snippet = `[✂${info.expression.start}-${info.expression.end}✂];`;
|
||||
this.expression = new Expression(component, this, template_scope, info.expression);
|
||||
this.usesContext = this.expression.usesContext;
|
||||
} else {
|
||||
this.callee = null;
|
||||
this.insertionPoint = null;
|
||||
const name = component.getUniqueName(`${this.name}_handler`);
|
||||
component.declarations.push(name);
|
||||
|
||||
this.args = null;
|
||||
this.usesComponent = true;
|
||||
this.usesContext = false;
|
||||
this.usesEventObject = true;
|
||||
component.partly_hoisted.push(deindent`
|
||||
function ${name}(event) {
|
||||
@bubble($$self, event);
|
||||
}
|
||||
`);
|
||||
|
||||
this.snippet = null; // TODO handle shorthand events here?
|
||||
this.handler_name = name;
|
||||
}
|
||||
|
||||
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?`;
|
||||
}
|
||||
}
|
||||
render() {
|
||||
if (this.expression) return this.expression.render();
|
||||
|
||||
component.warn(expression, {
|
||||
code: `invalid-callee`,
|
||||
message
|
||||
});
|
||||
this.component.template_references.add(this.handler_name);
|
||||
return `ctx.${this.handler_name}`;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import Node from './shared/Node';
|
||||
|
||||
export default class Meta extends Node {
|
||||
type: 'Meta';
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
import Node from './shared/Node';
|
||||
import Block from '../render-dom/Block';
|
||||
import mapChildren from './shared/mapChildren';
|
||||
import TemplateScope from './shared/TemplateScope';
|
||||
|
||||
export default class ThenBlock extends Node {
|
||||
block: Block;
|
||||
scope: TemplateScope;
|
||||
children: Node[];
|
||||
|
||||
constructor(component, parent, scope, info) {
|
||||
super(component, parent, scope, info);
|
||||
this.children = mapChildren(component, parent, scope, info.children);
|
||||
|
||||
this.scope = scope.child();
|
||||
this.scope.add(parent.value, parent.expression.dependencies);
|
||||
this.children = mapChildren(component, parent, this.scope, info.children);
|
||||
|
||||
this.warnIfEmptyBlock();
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
import Renderer from '../../Renderer';
|
||||
import Block from '../../Block';
|
||||
import Action from '../../../nodes/Action';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function addActions(
|
||||
component: Component,
|
||||
block: Block,
|
||||
target: string,
|
||||
actions: Action[]
|
||||
) {
|
||||
actions.forEach(action => {
|
||||
const { expression } = action;
|
||||
let snippet, dependencies;
|
||||
if (expression) {
|
||||
snippet = expression.render();
|
||||
dependencies = expression.dynamic_dependencies;
|
||||
|
||||
expression.declarations.forEach(declaration => {
|
||||
block.builders.init.addBlock(declaration);
|
||||
});
|
||||
}
|
||||
|
||||
const name = block.getUniqueName(
|
||||
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
|
||||
);
|
||||
|
||||
block.addVariable(name);
|
||||
const fn = component.imported_declarations.has(action.name) || component.hoistable_names.has(action.name)
|
||||
? action.name
|
||||
: `ctx.${action.name}`;
|
||||
|
||||
component.template_references.add(action.name);
|
||||
|
||||
block.builders.mount.addLine(
|
||||
`${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};`
|
||||
);
|
||||
|
||||
if (dependencies && dependencies.size > 0) {
|
||||
let conditional = `typeof ${name}.update === 'function' && `;
|
||||
const deps = [...dependencies].map(dependency => `changed.${dependency}`).join(' || ');
|
||||
conditional += dependencies.size > 1 ? `(${deps})` : deps;
|
||||
|
||||
block.builders.update.addConditional(
|
||||
conditional,
|
||||
`${name}.update.call(null, ${snippet});`
|
||||
);
|
||||
}
|
||||
|
||||
block.builders.destroy.addLine(
|
||||
`if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();`
|
||||
);
|
||||
});
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import Block from '../../Block';
|
||||
import EventHandler from '../../../nodes/EventHandler';
|
||||
|
||||
export default function addEventHandlers(
|
||||
block: Block,
|
||||
target: string,
|
||||
handlers: EventHandler[]
|
||||
) {
|
||||
handlers.forEach(handler => {
|
||||
let snippet = handler.render();
|
||||
if (handler.modifiers.has('preventDefault')) snippet = `@preventDefault(${snippet})`;
|
||||
if (handler.modifiers.has('stopPropagation')) snippet = `@stopPropagation(${snippet})`;
|
||||
|
||||
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));
|
||||
|
||||
if (opts.length) {
|
||||
const optString = (opts.length === 1 && opts[0] === 'capture')
|
||||
? 'true'
|
||||
: `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`;
|
||||
|
||||
block.event_listeners.push(
|
||||
`@addListener(${target}, "${handler.name}", ${snippet}, ${optString})`
|
||||
);
|
||||
} else {
|
||||
block.event_listeners.push(
|
||||
`@addListener(${target}, "${handler.name}", ${snippet})`
|
||||
);
|
||||
}
|
||||
|
||||
if (handler.expression) {
|
||||
handler.expression.declarations.forEach(declaration => {
|
||||
block.builders.init.addBlock(declaration);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
import Renderer from '../Renderer';
|
||||
import { CompileOptions } from '../../../interfaces';
|
||||
import { snip } from '../utils';
|
||||
|
||||
export default function(node, renderer: Renderer, options: CompileOptions) {
|
||||
const { snippet } = node.expression;
|
||||
|
||||
renderer.append('${(function(__value) { if(@isPromise(__value)) return `');
|
||||
|
||||
renderer.render(node.pending.children, options);
|
||||
|
||||
renderer.append('`; return function(ctx) { return `');
|
||||
renderer.append('`; return function(' + (node.value || '') + ') { return `');
|
||||
|
||||
renderer.render(node.then.children, options);
|
||||
|
||||
renderer.append(`\`;}(Object.assign({}, ctx, { ${node.value}: __value }));}(${snippet})) }`);
|
||||
const snippet = snip(node.expression);
|
||||
renderer.append(`\`;}(__value);}(${snippet})) }`);
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { snip } from '../utils';
|
||||
|
||||
export default function(node, renderer, options) {
|
||||
renderer.append('${' + node.expression.snippet + '}');
|
||||
renderer.append('${' + snip(node.expression) + '}');
|
||||
}
|
@ -1,9 +1,13 @@
|
||||
import { snip } from '../utils';
|
||||
|
||||
export default function(node, renderer, options) {
|
||||
const snippet = snip(node.expression);
|
||||
|
||||
renderer.append(
|
||||
node.parent &&
|
||||
node.parent.type === 'Element' &&
|
||||
node.parent.name === 'style'
|
||||
? '${' + node.expression.snippet + '}'
|
||||
: '${@escape(' + node.expression.snippet + ')}'
|
||||
? '${' + snippet + '}'
|
||||
: '${@escape(' + snippet + ')}'
|
||||
);
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export function snip(expression) {
|
||||
return `[✂${expression.node.start}-${expression.node.end}✂]`;
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import checkForDupes from '../utils/checkForDupes';
|
||||
import checkForComputedKeys from '../utils/checkForComputedKeys';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function actions(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ObjectExpression') {
|
||||
component.error(prop, {
|
||||
code: `invalid-actions`,
|
||||
message: `The 'actions' property must be an object literal`
|
||||
});
|
||||
}
|
||||
|
||||
checkForDupes(component, prop.value.properties);
|
||||
checkForComputedKeys(component, prop.value.properties);
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import checkForDupes from '../utils/checkForDupes';
|
||||
import checkForComputedKeys from '../utils/checkForComputedKeys';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function transitions(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ObjectExpression') {
|
||||
component.error(prop, {
|
||||
code: `invalid-transitions-property`,
|
||||
message: `The 'transitions' property must be an object literal`
|
||||
});
|
||||
}
|
||||
|
||||
checkForDupes(component, prop.value.properties);
|
||||
checkForComputedKeys(component, prop.value.properties);
|
||||
|
||||
prop.value.properties.forEach(() => {
|
||||
// TODO probably some validation that can happen here...
|
||||
// checking for use of `this` etc?
|
||||
});
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import checkForDupes from '../utils/checkForDupes';
|
||||
import checkForComputedKeys from '../utils/checkForComputedKeys';
|
||||
import getName from '../../../../utils/getName';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function components(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ObjectExpression') {
|
||||
component.error(prop, {
|
||||
code: `invalid-components-property`,
|
||||
message: `The 'components' property must be an object literal`
|
||||
});
|
||||
}
|
||||
|
||||
checkForDupes(component, prop.value.properties);
|
||||
checkForComputedKeys(component, prop.value.properties);
|
||||
|
||||
prop.value.properties.forEach((node: Node) => {
|
||||
const name = getName(node.key);
|
||||
|
||||
if (name === 'state') {
|
||||
// TODO is this still true?
|
||||
component.error(node, {
|
||||
code: `invalid-name`,
|
||||
message: `Component constructors cannot be called 'state' due to technical limitations`
|
||||
});
|
||||
}
|
||||
|
||||
if (!/^[A-Z]/.test(name)) {
|
||||
component.error(node, {
|
||||
code: `component-lowercase`,
|
||||
message: `Component names must be capitalised`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import checkForDupes from '../utils/checkForDupes';
|
||||
import checkForComputedKeys from '../utils/checkForComputedKeys';
|
||||
import getName from '../../../../utils/getName';
|
||||
import isValidIdentifier from '../../../../utils/isValidIdentifier';
|
||||
import reservedNames from '../../../../utils/reservedNames';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import walkThroughTopFunctionScope from '../../../../utils/walkThroughTopFunctionScope';
|
||||
import isThisGetCallExpression from '../../../../utils/isThisGetCallExpression';
|
||||
import Component from '../../../Component';
|
||||
|
||||
const isFunctionExpression = new Set([
|
||||
'FunctionExpression',
|
||||
'ArrowFunctionExpression',
|
||||
]);
|
||||
|
||||
export default function computed(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ObjectExpression') {
|
||||
component.error(prop, {
|
||||
code: `invalid-computed-property`,
|
||||
message: `The 'computed' property must be an object literal`
|
||||
});
|
||||
}
|
||||
|
||||
checkForDupes(component, prop.value.properties);
|
||||
checkForComputedKeys(component, prop.value.properties);
|
||||
|
||||
prop.value.properties.forEach((computation: Node) => {
|
||||
const name = getName(computation.key);
|
||||
|
||||
if (!isValidIdentifier(name)) {
|
||||
const suggestion = name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&');
|
||||
component.error(computation.key, {
|
||||
code: `invalid-computed-name`,
|
||||
message: `Computed property name '${name}' is invalid — must be a valid identifier such as ${suggestion}`
|
||||
});
|
||||
}
|
||||
|
||||
if (reservedNames.has(name)) {
|
||||
component.error(computation.key, {
|
||||
code: `invalid-computed-name`,
|
||||
message: `Computed property name '${name}' is invalid — cannot be a JavaScript reserved word`
|
||||
});
|
||||
}
|
||||
|
||||
if (!isFunctionExpression.has(computation.value.type)) {
|
||||
component.error(computation.value, {
|
||||
code: `invalid-computed-value`,
|
||||
message: `Computed properties can be function expressions or arrow function expressions`
|
||||
});
|
||||
}
|
||||
|
||||
const { body, params } = computation.value;
|
||||
|
||||
walkThroughTopFunctionScope(body, (node: Node) => {
|
||||
if (isThisGetCallExpression(node) && !node.callee.property.computed) {
|
||||
component.error(node, {
|
||||
code: `impure-computed`,
|
||||
message: `Cannot use this.get(...) — values must be passed into the function as arguments`
|
||||
});
|
||||
}
|
||||
|
||||
if (node.type === 'ThisExpression') {
|
||||
component.error(node, {
|
||||
code: `impure-computed`,
|
||||
message: `Computed properties should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (params.length === 0) {
|
||||
component.error(computation.value, {
|
||||
code: `impure-computed`,
|
||||
message: `A computed value must depend on at least one property`
|
||||
});
|
||||
}
|
||||
|
||||
if (params.length > 1) {
|
||||
component.error(computation.value, {
|
||||
code: `invalid-computed-arguments`,
|
||||
message: `Computed properties must take a single argument`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
const disallowed = new Set(['Literal', 'ObjectExpression', 'ArrayExpression']);
|
||||
|
||||
export default function data(component: Component, prop: Node) {
|
||||
while (prop.type === 'ParenthesizedExpression') prop = prop.expression;
|
||||
|
||||
if (disallowed.has(prop.value.type)) {
|
||||
component.error(prop.value, {
|
||||
code: `invalid-data-property`,
|
||||
message: `'data' must be a function`
|
||||
});
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import checkForDupes from '../utils/checkForDupes';
|
||||
import checkForComputedKeys from '../utils/checkForComputedKeys';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function events(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ObjectExpression') {
|
||||
component.error(prop, {
|
||||
code: `invalid-events-property`,
|
||||
message: `The 'events' property must be an object literal`
|
||||
});
|
||||
}
|
||||
|
||||
checkForDupes(component, prop.value.properties);
|
||||
checkForComputedKeys(component, prop.value.properties);
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import checkForDupes from '../utils/checkForDupes';
|
||||
import checkForComputedKeys from '../utils/checkForComputedKeys';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import walkThroughTopFunctionScope from '../../../../utils/walkThroughTopFunctionScope';
|
||||
import isThisGetCallExpression from '../../../../utils/isThisGetCallExpression';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function helpers(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ObjectExpression') {
|
||||
component.error(prop, {
|
||||
code: `invalid-helpers-property`,
|
||||
message: `The 'helpers' property must be an object literal`
|
||||
});
|
||||
}
|
||||
|
||||
checkForDupes(component, prop.value.properties);
|
||||
checkForComputedKeys(component, prop.value.properties);
|
||||
|
||||
prop.value.properties.forEach((prop: Node) => {
|
||||
if (!/FunctionExpression/.test(prop.value.type)) return;
|
||||
|
||||
let usesArguments = false;
|
||||
|
||||
walkThroughTopFunctionScope(prop.value.body, (node: Node) => {
|
||||
if (isThisGetCallExpression(node) && !node.callee.property.computed) {
|
||||
component.error(node, {
|
||||
code: `impure-helper`,
|
||||
message: `Cannot use this.get(...) — values must be passed into the helper function as arguments`
|
||||
});
|
||||
}
|
||||
|
||||
if (node.type === 'ThisExpression') {
|
||||
component.error(node, {
|
||||
code: `impure-helper`,
|
||||
message: `Helpers should be pure functions — they do not have access to the component instance and cannot use 'this'. Did you mean to put this in 'methods'?`
|
||||
});
|
||||
} else if (node.type === 'Identifier' && node.name === 'arguments') {
|
||||
usesArguments = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (prop.value.params.length === 0 && !usesArguments) {
|
||||
component.warn(prop, {
|
||||
code: `impure-helper`,
|
||||
message: `Helpers should be pure functions, with at least one argument`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function immutable(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'Literal' || typeof prop.value.value !== 'boolean') {
|
||||
component.error(prop.value, {
|
||||
code: `invalid-immutable-property`,
|
||||
message: `'immutable' must be a boolean literal`
|
||||
});
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import data from './data';
|
||||
import actions from './actions';
|
||||
import animations from './animations';
|
||||
import computed from './computed';
|
||||
import oncreate from './oncreate';
|
||||
import ondestroy from './ondestroy';
|
||||
import onstate from './onstate';
|
||||
import onupdate from './onupdate';
|
||||
import onrender from './onrender';
|
||||
import onteardown from './onteardown';
|
||||
import helpers from './helpers';
|
||||
import methods from './methods';
|
||||
import components from './components';
|
||||
import events from './events';
|
||||
import namespace from './namespace';
|
||||
import preload from './preload';
|
||||
import props from './props';
|
||||
import tag from './tag';
|
||||
import transitions from './transitions';
|
||||
import setup from './setup';
|
||||
import store from './store';
|
||||
import immutable from './immutable';
|
||||
|
||||
export default {
|
||||
data,
|
||||
actions,
|
||||
animations,
|
||||
computed,
|
||||
oncreate,
|
||||
ondestroy,
|
||||
onstate,
|
||||
onupdate,
|
||||
onrender,
|
||||
onteardown,
|
||||
helpers,
|
||||
methods,
|
||||
components,
|
||||
events,
|
||||
namespace,
|
||||
preload,
|
||||
props,
|
||||
tag,
|
||||
transitions,
|
||||
setup,
|
||||
store,
|
||||
immutable,
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import checkForAccessors from '../utils/checkForAccessors';
|
||||
import checkForDupes from '../utils/checkForDupes';
|
||||
import checkForComputedKeys from '../utils/checkForComputedKeys';
|
||||
import usesThisOrArguments from '../utils/usesThisOrArguments';
|
||||
import getName from '../../../../utils/getName';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
const builtin = new Set(['set', 'get', 'on', 'fire', 'destroy']);
|
||||
|
||||
export default function methods(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ObjectExpression') {
|
||||
component.error(prop, {
|
||||
code: `invalid-methods-property`,
|
||||
message: `The 'methods' property must be an object literal`
|
||||
});
|
||||
}
|
||||
|
||||
checkForAccessors(component, prop.value.properties, 'Methods');
|
||||
checkForDupes(component, prop.value.properties);
|
||||
checkForComputedKeys(component, prop.value.properties);
|
||||
|
||||
prop.value.properties.forEach((prop: Node) => {
|
||||
const name = getName(prop.key);
|
||||
|
||||
if (builtin.has(name)) {
|
||||
component.error(prop, {
|
||||
code: `invalid-method-name`,
|
||||
message: `Cannot overwrite built-in method '${name}'`
|
||||
});
|
||||
}
|
||||
|
||||
if (prop.value.type === 'ArrowFunctionExpression') {
|
||||
if (usesThisOrArguments(prop.value.body)) {
|
||||
component.error(prop, {
|
||||
code: `invalid-method-value`,
|
||||
message: `Method '${prop.key.name}' should be a function expression, not an arrow function expression`
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import * as namespaces from '../../../../utils/namespaces';
|
||||
import nodeToString from '../../../../utils/nodeToString'
|
||||
import fuzzymatch from '../../utils/fuzzymatch';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
const valid = new Set(namespaces.validNamespaces);
|
||||
|
||||
export default function namespace(component: Component, prop: Node) {
|
||||
const ns = nodeToString(prop.value);
|
||||
|
||||
if (typeof ns !== 'string') {
|
||||
component.error(prop, {
|
||||
code: `invalid-namespace-property`,
|
||||
message: `The 'namespace' property must be a string literal representing a valid namespace`
|
||||
});
|
||||
}
|
||||
|
||||
if (!valid.has(ns)) {
|
||||
const match = fuzzymatch(ns, namespaces.validNamespaces);
|
||||
if (match) {
|
||||
component.error(prop, {
|
||||
code: `invalid-namespace-property`,
|
||||
message: `Invalid namespace '${ns}' (did you mean '${match}'?)`
|
||||
});
|
||||
} else {
|
||||
component.error(prop, {
|
||||
code: `invalid-namespace-property`,
|
||||
message: `Invalid namespace '${ns}'`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import usesThisOrArguments from '../utils/usesThisOrArguments';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function oncreate(component: Component, prop: Node) {
|
||||
if (prop.value.type === 'ArrowFunctionExpression') {
|
||||
if (usesThisOrArguments(prop.value.body)) {
|
||||
component.error(prop, {
|
||||
code: `invalid-oncreate-property`,
|
||||
message: `'oncreate' should be a function expression, not an arrow function expression`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import usesThisOrArguments from '../utils/usesThisOrArguments';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function ondestroy(component: Component, prop: Node) {
|
||||
if (prop.value.type === 'ArrowFunctionExpression') {
|
||||
if (usesThisOrArguments(prop.value.body)) {
|
||||
component.error(prop, {
|
||||
code: `invalid-ondestroy-property`,
|
||||
message: `'ondestroy' should be a function expression, not an arrow function expression`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import oncreate from './oncreate';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function onrender(component: Component, prop: Node) {
|
||||
component.warn(prop, {
|
||||
code: `deprecated-onrender`,
|
||||
message: `'onrender' has been deprecated in favour of 'oncreate', and will cause an error in Svelte 2.x`
|
||||
});
|
||||
|
||||
oncreate(component, prop);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import usesThisOrArguments from '../utils/usesThisOrArguments';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function onstate(component: Component, prop: Node) {
|
||||
if (prop.value.type === 'ArrowFunctionExpression') {
|
||||
if (usesThisOrArguments(prop.value.body)) {
|
||||
component.error(prop, {
|
||||
code: `invalid-onstate-property`,
|
||||
message: `'onstate' should be a function expression, not an arrow function expression`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import ondestroy from './ondestroy';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function onteardown(component: Component, prop: Node) {
|
||||
component.warn(prop, {
|
||||
code: `deprecated-onteardown`,
|
||||
message: `'onteardown' has been deprecated in favour of 'ondestroy', and will cause an error in Svelte 2.x`
|
||||
});
|
||||
|
||||
ondestroy(component, prop);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import usesThisOrArguments from '../utils/usesThisOrArguments';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function onupdate(component: Component, prop: Node) {
|
||||
if (prop.value.type === 'ArrowFunctionExpression') {
|
||||
if (usesThisOrArguments(prop.value.body)) {
|
||||
component.error(prop, {
|
||||
code: `invalid-onupdate-property`,
|
||||
message: `'onupdate' should be a function expression, not an arrow function expression`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function preload(component: Component, prop: Node) {
|
||||
// not sure there's anything we need to check here...
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import nodeToString from '../../../../utils/nodeToString';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function props(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ArrayExpression') {
|
||||
component.error(prop.value, {
|
||||
code: `invalid-props-property`,
|
||||
message: `'props' must be an array expression, if specified`
|
||||
});
|
||||
}
|
||||
|
||||
prop.value.elements.forEach((element: Node) => {
|
||||
if (typeof nodeToString(element) !== 'string') {
|
||||
component.error(element, {
|
||||
code: `invalid-props-property`,
|
||||
message: `'props' must be an array of string literals`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
const disallowed = new Set(['Literal', 'ObjectExpression', 'ArrayExpression']);
|
||||
|
||||
export default function setup(component: Component, prop: Node) {
|
||||
while (prop.type === 'ParenthesizedExpression') prop = prop.expression;
|
||||
|
||||
if (disallowed.has(prop.value.type)) {
|
||||
component.error(prop.value, {
|
||||
code: `invalid-setup-property`,
|
||||
message: `'setup' must be a function`
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function store(component: Component, prop: Node) {
|
||||
// not sure there's anything we need to check here...
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import nodeToString from '../../../../utils/nodeToString';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function tag(component: Component, prop: Node) {
|
||||
const tag = nodeToString(prop.value);
|
||||
if (typeof tag !== 'string') {
|
||||
component.error(prop.value, {
|
||||
code: `invalid-tag-property`,
|
||||
message: `'tag' must be a string literal`
|
||||
});
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
|
||||
component.error(prop.value, {
|
||||
code: `invalid-tag-property`,
|
||||
message: `tag name must be two or more words joined by the '-' character`
|
||||
});
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import checkForDupes from '../utils/checkForDupes';
|
||||
import checkForComputedKeys from '../utils/checkForComputedKeys';
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function transitions(component: Component, prop: Node) {
|
||||
if (prop.value.type !== 'ObjectExpression') {
|
||||
component.error(prop, {
|
||||
code: `invalid-transitions-property`,
|
||||
message: `The 'transitions' property must be an object literal`
|
||||
});
|
||||
}
|
||||
|
||||
checkForDupes(component, prop.value.properties);
|
||||
checkForComputedKeys(component, prop.value.properties);
|
||||
|
||||
prop.value.properties.forEach(() => {
|
||||
// TODO probably some validation that can happen here...
|
||||
// checking for use of `this` etc?
|
||||
});
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function checkForAccessors(
|
||||
component: Component,
|
||||
properties: Node[],
|
||||
label: string
|
||||
) {
|
||||
properties.forEach(prop => {
|
||||
if (prop.kind !== 'init') {
|
||||
component.error(prop, {
|
||||
code: `illegal-accessor`,
|
||||
message: `${label} cannot use getters and setters`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function checkForComputedKeys(
|
||||
component: Component,
|
||||
properties: Node[]
|
||||
) {
|
||||
properties.forEach(prop => {
|
||||
if (prop.key.computed) {
|
||||
component.error(prop, {
|
||||
code: `computed-key`,
|
||||
message: `Cannot use computed keys`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { Node } from '../../../../interfaces';
|
||||
import getName from '../../../../utils/getName';
|
||||
import Component from '../../../Component';
|
||||
|
||||
export default function checkForDupes(
|
||||
component: Component,
|
||||
properties: Node[]
|
||||
) {
|
||||
const seen = new Set();
|
||||
|
||||
properties.forEach(prop => {
|
||||
const name = getName(prop.key);
|
||||
|
||||
if (seen.has(name)) {
|
||||
component.error(prop, {
|
||||
code: `duplicate-property`,
|
||||
message: `Duplicate property '${name}'`
|
||||
});
|
||||
}
|
||||
|
||||
seen.add(name);
|
||||
});
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import { walk } from 'estree-walker';
|
||||
import isReference from 'is-reference';
|
||||
import { Node } from '../../../../interfaces';
|
||||
|
||||
export default function usesThisOrArguments(node: Node) {
|
||||
let result = false;
|
||||
|
||||
walk(node, {
|
||||
enter(node: Node, parent: Node) {
|
||||
if (
|
||||
result ||
|
||||
node.type === 'FunctionExpression' ||
|
||||
node.type === 'FunctionDeclaration'
|
||||
) {
|
||||
return this.skip();
|
||||
}
|
||||
|
||||
if (node.type === 'ThisExpression') {
|
||||
result = true;
|
||||
}
|
||||
|
||||
if (
|
||||
node.type === 'Identifier' &&
|
||||
isReference(node, parent) &&
|
||||
node.name === 'arguments'
|
||||
) {
|
||||
result = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import FuzzySet from './FuzzySet';
|
||||
|
||||
export default function fuzzymatch(name: string, names: string[]) {
|
||||
const set = new FuzzySet(names);
|
||||
const matches = set.get(name);
|
||||
|
||||
return matches && matches[0] && matches[0][0] > 0.7 ? matches[0][1] : null;
|
||||
}
|
@ -1,34 +1,4 @@
|
||||
import compile from './compile/index';
|
||||
import { CompileOptions } from './interfaces';
|
||||
import deprecate from './utils/deprecate';
|
||||
|
||||
export function create(source: string, options: CompileOptions = {}) {
|
||||
const onerror = options.onerror || (err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
if (options.onerror) {
|
||||
// TODO remove in v3
|
||||
deprecate(`Instead of using options.onerror, wrap svelte.create in a try-catch block`);
|
||||
delete options.onerror;
|
||||
}
|
||||
|
||||
options.format = 'eval';
|
||||
|
||||
try {
|
||||
const compiled = compile(source, options);
|
||||
|
||||
if (!compiled || !compiled.js.code) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (new Function(`return ${compiled.js.code}`))();
|
||||
} catch (err) {
|
||||
onerror(err);
|
||||
}
|
||||
}
|
||||
|
||||
export { default as compile } from './compile/index';
|
||||
export { default as parse } from './parse/index';
|
||||
export { default as preprocess } from './preprocess/index';
|
||||
|
||||
export const VERSION = '__VERSION__';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue