move some files over

pull/1721/head
Rich Harris 7 years ago
parent 98079fb782
commit 9dcab952af

@ -22,10 +22,10 @@ import { SsrTarget } from './ssr';
import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces'; import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces';
import error from '../utils/error'; import error from '../utils/error';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import checkForComputedKeys from '../validate/js/utils/checkForComputedKeys'; import checkForComputedKeys from './validate/js/utils/checkForComputedKeys';
import checkForDupes from '../validate/js/utils/checkForDupes'; import checkForDupes from './validate/js/utils/checkForDupes';
import propValidators from '../validate/js/propValidators'; import propValidators from './validate/js/propValidators';
import fuzzymatch from '../validate/utils/fuzzymatch'; import fuzzymatch from './validate/utils/fuzzymatch';
import flattenReference from '../utils/flattenReference'; import flattenReference from '../utils/flattenReference';
interface Computation { interface Computation {

@ -20,7 +20,7 @@ import Text from './Text';
import * as namespaces from '../../utils/namespaces'; import * as namespaces from '../../utils/namespaces';
import mapChildren from './shared/mapChildren'; import mapChildren from './shared/mapChildren';
import { dimensions } from '../../utils/patterns'; import { dimensions } from '../../utils/patterns';
import fuzzymatch from '../../validate/utils/fuzzymatch'; import fuzzymatch from '../validate/utils/fuzzymatch';
import Ref from './Ref'; import Ref from './Ref';
// source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7 // source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7

@ -4,7 +4,7 @@ import Block from '../dom/Block';
import Binding from './Binding'; import Binding from './Binding';
import EventHandler from './EventHandler'; import EventHandler from './EventHandler';
import flattenReference from '../../utils/flattenReference'; import flattenReference from '../../utils/flattenReference';
import fuzzymatch from '../../validate/utils/fuzzymatch'; import fuzzymatch from '../validate/utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
const associatedEvents = { const associatedEvents = {

@ -0,0 +1,16 @@
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,18 +1,18 @@
import checkForDupes from '../utils/checkForDupes'; import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys'; import checkForComputedKeys from '../utils/checkForComputedKeys';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function transitions(validator: Validator, prop: Node) { export default function transitions(component: Component, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { component.error(prop, {
code: `invalid-transitions-property`, code: `invalid-transitions-property`,
message: `The 'transitions' property must be an object literal` message: `The 'transitions' property must be an object literal`
}); });
} }
checkForDupes(validator, prop.value.properties); checkForDupes(component, prop.value.properties);
checkForComputedKeys(validator, prop.value.properties); checkForComputedKeys(component, prop.value.properties);
prop.value.properties.forEach(() => { prop.value.properties.forEach(() => {
// TODO probably some validation that can happen here... // TODO probably some validation that can happen here...

@ -1,33 +1,33 @@
import checkForDupes from '../utils/checkForDupes'; import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys'; import checkForComputedKeys from '../utils/checkForComputedKeys';
import getName from '../../../utils/getName'; import getName from '../../../../utils/getName';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function components(validator: Validator, prop: Node) { export default function components(component: Component, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { component.error(prop, {
code: `invalid-components-property`, code: `invalid-components-property`,
message: `The 'components' property must be an object literal` message: `The 'components' property must be an object literal`
}); });
} }
checkForDupes(validator, prop.value.properties); checkForDupes(component, prop.value.properties);
checkForComputedKeys(validator, prop.value.properties); checkForComputedKeys(component, prop.value.properties);
prop.value.properties.forEach((component: Node) => { prop.value.properties.forEach((node: Node) => {
const name = getName(component.key); const name = getName(node.key);
if (name === 'state') { if (name === 'state') {
// TODO is this still true? // TODO is this still true?
validator.error(component, { component.error(node, {
code: `invalid-name`, code: `invalid-name`,
message: `Component constructors cannot be called 'state' due to technical limitations` message: `Component constructors cannot be called 'state' due to technical limitations`
}); });
} }
if (!/^[A-Z]/.test(name)) { if (!/^[A-Z]/.test(name)) {
validator.error(component, { component.error(node, {
code: `component-lowercase`, code: `component-lowercase`,
message: `Component names must be capitalised` message: `Component names must be capitalised`
}); });

@ -1,50 +1,49 @@
import checkForDupes from '../utils/checkForDupes'; import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys'; import checkForComputedKeys from '../utils/checkForComputedKeys';
import getName from '../../../utils/getName'; import getName from '../../../../utils/getName';
import isValidIdentifier from '../../../utils/isValidIdentifier'; import isValidIdentifier from '../../../../utils/isValidIdentifier';
import reservedNames from '../../../utils/reservedNames'; import reservedNames from '../../../../utils/reservedNames';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import walkThroughTopFunctionScope from '../../../../utils/walkThroughTopFunctionScope';
import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope'; import isThisGetCallExpression from '../../../../utils/isThisGetCallExpression';
import isThisGetCallExpression from '../../../utils/isThisGetCallExpression'; import Component from '../../../Component';
import validCalleeObjects from '../../../utils/validCalleeObjects';
const isFunctionExpression = new Set([ const isFunctionExpression = new Set([
'FunctionExpression', 'FunctionExpression',
'ArrowFunctionExpression', 'ArrowFunctionExpression',
]); ]);
export default function computed(validator: Validator, prop: Node) { export default function computed(component: Component, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { component.error(prop, {
code: `invalid-computed-property`, code: `invalid-computed-property`,
message: `The 'computed' property must be an object literal` message: `The 'computed' property must be an object literal`
}); });
} }
checkForDupes(validator, prop.value.properties); checkForDupes(component, prop.value.properties);
checkForComputedKeys(validator, prop.value.properties); checkForComputedKeys(component, prop.value.properties);
prop.value.properties.forEach((computation: Node) => { prop.value.properties.forEach((computation: Node) => {
const name = getName(computation.key); const name = getName(computation.key);
if (!isValidIdentifier(name)) { if (!isValidIdentifier(name)) {
const suggestion = name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&'); const suggestion = name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&');
validator.error(computation.key, { component.error(computation.key, {
code: `invalid-computed-name`, code: `invalid-computed-name`,
message: `Computed property name '${name}' is invalid — must be a valid identifier such as ${suggestion}` message: `Computed property name '${name}' is invalid — must be a valid identifier such as ${suggestion}`
}); });
} }
if (reservedNames.has(name)) { if (reservedNames.has(name)) {
validator.error(computation.key, { component.error(computation.key, {
code: `invalid-computed-name`, code: `invalid-computed-name`,
message: `Computed property name '${name}' is invalid — cannot be a JavaScript reserved word` message: `Computed property name '${name}' is invalid — cannot be a JavaScript reserved word`
}); });
} }
if (!isFunctionExpression.has(computation.value.type)) { if (!isFunctionExpression.has(computation.value.type)) {
validator.error(computation.value, { component.error(computation.value, {
code: `invalid-computed-value`, code: `invalid-computed-value`,
message: `Computed properties can be function expressions or arrow function expressions` message: `Computed properties can be function expressions or arrow function expressions`
}); });
@ -54,14 +53,14 @@ export default function computed(validator: Validator, prop: Node) {
walkThroughTopFunctionScope(body, (node: Node) => { walkThroughTopFunctionScope(body, (node: Node) => {
if (isThisGetCallExpression(node) && !node.callee.property.computed) { if (isThisGetCallExpression(node) && !node.callee.property.computed) {
validator.error(node, { component.error(node, {
code: `impure-computed`, code: `impure-computed`,
message: `Cannot use this.get(...) — values must be passed into the function as arguments` message: `Cannot use this.get(...) — values must be passed into the function as arguments`
}); });
} }
if (node.type === 'ThisExpression') { if (node.type === 'ThisExpression') {
validator.error(node, { component.error(node, {
code: `impure-computed`, 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'?` 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'?`
}); });
@ -69,14 +68,14 @@ export default function computed(validator: Validator, prop: Node) {
}); });
if (params.length === 0) { if (params.length === 0) {
validator.error(computation.value, { component.error(computation.value, {
code: `impure-computed`, code: `impure-computed`,
message: `A computed value must depend on at least one property` message: `A computed value must depend on at least one property`
}); });
} }
if (params.length > 1) { if (params.length > 1) {
validator.error(computation.value, { component.error(computation.value, {
code: `invalid-computed-arguments`, code: `invalid-computed-arguments`,
message: `Computed properties must take a single argument` message: `Computed properties must take a single argument`
}); });

@ -1,13 +1,13 @@
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
const disallowed = new Set(['Literal', 'ObjectExpression', 'ArrayExpression']); const disallowed = new Set(['Literal', 'ObjectExpression', 'ArrayExpression']);
export default function data(validator: Validator, prop: Node) { export default function data(component: Component, prop: Node) {
while (prop.type === 'ParenthesizedExpression') prop = prop.expression; while (prop.type === 'ParenthesizedExpression') prop = prop.expression;
if (disallowed.has(prop.value.type)) { if (disallowed.has(prop.value.type)) {
validator.error(prop.value, { component.error(prop.value, {
code: `invalid-data-property`, code: `invalid-data-property`,
message: `'data' must be a function` message: `'data' must be a function`
}); });

@ -0,0 +1,16 @@
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,21 +1,20 @@
import checkForDupes from '../utils/checkForDupes'; import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys'; import checkForComputedKeys from '../utils/checkForComputedKeys';
import { walk } from 'estree-walker'; import { Node } from '../../../../interfaces';
import { Validator } from '../../index'; import walkThroughTopFunctionScope from '../../../../utils/walkThroughTopFunctionScope';
import { Node } from '../../../interfaces'; import isThisGetCallExpression from '../../../../utils/isThisGetCallExpression';
import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope'; import Component from '../../../Component';
import isThisGetCallExpression from '../../../utils/isThisGetCallExpression';
export default function helpers(validator: Validator, prop: Node) { export default function helpers(component: Component, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { component.error(prop, {
code: `invalid-helpers-property`, code: `invalid-helpers-property`,
message: `The 'helpers' property must be an object literal` message: `The 'helpers' property must be an object literal`
}); });
} }
checkForDupes(validator, prop.value.properties); checkForDupes(component, prop.value.properties);
checkForComputedKeys(validator, prop.value.properties); checkForComputedKeys(component, prop.value.properties);
prop.value.properties.forEach((prop: Node) => { prop.value.properties.forEach((prop: Node) => {
if (!/FunctionExpression/.test(prop.value.type)) return; if (!/FunctionExpression/.test(prop.value.type)) return;
@ -24,14 +23,14 @@ export default function helpers(validator: Validator, prop: Node) {
walkThroughTopFunctionScope(prop.value.body, (node: Node) => { walkThroughTopFunctionScope(prop.value.body, (node: Node) => {
if (isThisGetCallExpression(node) && !node.callee.property.computed) { if (isThisGetCallExpression(node) && !node.callee.property.computed) {
validator.error(node, { component.error(node, {
code: `impure-helper`, code: `impure-helper`,
message: `Cannot use this.get(...) — values must be passed into the helper function as arguments` message: `Cannot use this.get(...) — values must be passed into the helper function as arguments`
}); });
} }
if (node.type === 'ThisExpression') { if (node.type === 'ThisExpression') {
validator.error(node, { component.error(node, {
code: `impure-helper`, 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'?` 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'?`
}); });
@ -41,7 +40,7 @@ export default function helpers(validator: Validator, prop: Node) {
}); });
if (prop.value.params.length === 0 && !usesArguments) { if (prop.value.params.length === 0 && !usesArguments) {
validator.warn(prop, { component.warn(prop, {
code: `impure-helper`, code: `impure-helper`,
message: `Helpers should be pure functions, with at least one argument` message: `Helpers should be pure functions, with at least one argument`
}); });

@ -1,9 +1,9 @@
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function immutable(validator: Validator, prop: Node) { export default function immutable(component: Component, prop: Node) {
if (prop.value.type !== 'Literal' || typeof prop.value.value !== 'boolean') { if (prop.value.type !== 'Literal' || typeof prop.value.value !== 'boolean') {
validator.error(prop.value, { component.error(prop.value, {
code: `invalid-immutable-property`, code: `invalid-immutable-property`,
message: `'immutable' must be a boolean literal` message: `'immutable' must be a boolean literal`
}); });

@ -2,29 +2,29 @@ import checkForAccessors from '../utils/checkForAccessors';
import checkForDupes from '../utils/checkForDupes'; import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys'; import checkForComputedKeys from '../utils/checkForComputedKeys';
import usesThisOrArguments from '../utils/usesThisOrArguments'; import usesThisOrArguments from '../utils/usesThisOrArguments';
import getName from '../../../utils/getName'; import getName from '../../../../utils/getName';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
const builtin = new Set(['set', 'get', 'on', 'fire', 'destroy']); const builtin = new Set(['set', 'get', 'on', 'fire', 'destroy']);
export default function methods(validator: Validator, prop: Node) { export default function methods(component: Component, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { component.error(prop, {
code: `invalid-methods-property`, code: `invalid-methods-property`,
message: `The 'methods' property must be an object literal` message: `The 'methods' property must be an object literal`
}); });
} }
checkForAccessors(validator, prop.value.properties, 'Methods'); checkForAccessors(component, prop.value.properties, 'Methods');
checkForDupes(validator, prop.value.properties); checkForDupes(component, prop.value.properties);
checkForComputedKeys(validator, prop.value.properties); checkForComputedKeys(component, prop.value.properties);
prop.value.properties.forEach((prop: Node) => { prop.value.properties.forEach((prop: Node) => {
const name = getName(prop.key); const name = getName(prop.key);
if (builtin.has(name)) { if (builtin.has(name)) {
validator.error(prop, { component.error(prop, {
code: `invalid-method-name`, code: `invalid-method-name`,
message: `Cannot overwrite built-in method '${name}'` message: `Cannot overwrite built-in method '${name}'`
}); });
@ -32,7 +32,7 @@ export default function methods(validator: Validator, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, { component.error(prop, {
code: `invalid-method-value`, code: `invalid-method-value`,
message: `Method '${prop.key.name}' should be a function expression, not an arrow function expression` message: `Method '${prop.key.name}' should be a function expression, not an arrow function expression`
}); });

@ -1,16 +1,16 @@
import * as namespaces from '../../../utils/namespaces'; import * as namespaces from '../../../../utils/namespaces';
import nodeToString from '../../../utils/nodeToString' import nodeToString from '../../../../utils/nodeToString'
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
const valid = new Set(namespaces.validNamespaces); const valid = new Set(namespaces.validNamespaces);
export default function namespace(validator: Validator, prop: Node) { export default function namespace(component: Component, prop: Node) {
const ns = nodeToString(prop.value); const ns = nodeToString(prop.value);
if (typeof ns !== 'string') { if (typeof ns !== 'string') {
validator.error(prop, { component.error(prop, {
code: `invalid-namespace-property`, code: `invalid-namespace-property`,
message: `The 'namespace' property must be a string literal representing a valid namespace` message: `The 'namespace' property must be a string literal representing a valid namespace`
}); });
@ -19,12 +19,12 @@ export default function namespace(validator: Validator, prop: Node) {
if (!valid.has(ns)) { if (!valid.has(ns)) {
const match = fuzzymatch(ns, namespaces.validNamespaces); const match = fuzzymatch(ns, namespaces.validNamespaces);
if (match) { if (match) {
validator.error(prop, { component.error(prop, {
code: `invalid-namespace-property`, code: `invalid-namespace-property`,
message: `Invalid namespace '${ns}' (did you mean '${match}'?)` message: `Invalid namespace '${ns}' (did you mean '${match}'?)`
}); });
} else { } else {
validator.error(prop, { component.error(prop, {
code: `invalid-namespace-property`, code: `invalid-namespace-property`,
message: `Invalid namespace '${ns}'` message: `Invalid namespace '${ns}'`
}); });

@ -1,11 +1,11 @@
import usesThisOrArguments from '../utils/usesThisOrArguments'; import usesThisOrArguments from '../utils/usesThisOrArguments';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function oncreate(validator: Validator, prop: Node) { export default function oncreate(component: Component, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, { component.error(prop, {
code: `invalid-oncreate-property`, code: `invalid-oncreate-property`,
message: `'oncreate' should be a function expression, not an arrow function expression` message: `'oncreate' should be a function expression, not an arrow function expression`
}); });

@ -1,11 +1,11 @@
import usesThisOrArguments from '../utils/usesThisOrArguments'; import usesThisOrArguments from '../utils/usesThisOrArguments';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function ondestroy(validator: Validator, prop: Node) { export default function ondestroy(component: Component, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, { component.error(prop, {
code: `invalid-ondestroy-property`, code: `invalid-ondestroy-property`,
message: `'ondestroy' should be a function expression, not an arrow function expression` message: `'ondestroy' should be a function expression, not an arrow function expression`
}); });

@ -0,0 +1,12 @@
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,11 +1,11 @@
import usesThisOrArguments from '../utils/usesThisOrArguments'; import usesThisOrArguments from '../utils/usesThisOrArguments';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function onstate(validator: Validator, prop: Node) { export default function onstate(component: Component, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, { component.error(prop, {
code: `invalid-onstate-property`, code: `invalid-onstate-property`,
message: `'onstate' should be a function expression, not an arrow function expression` message: `'onstate' should be a function expression, not an arrow function expression`
}); });

@ -0,0 +1,12 @@
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,11 +1,11 @@
import usesThisOrArguments from '../utils/usesThisOrArguments'; import usesThisOrArguments from '../utils/usesThisOrArguments';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function onupdate(validator: Validator, prop: Node) { export default function onupdate(component: Component, prop: Node) {
if (prop.value.type === 'ArrowFunctionExpression') { if (prop.value.type === 'ArrowFunctionExpression') {
if (usesThisOrArguments(prop.value.body)) { if (usesThisOrArguments(prop.value.body)) {
validator.error(prop, { component.error(prop, {
code: `invalid-onupdate-property`, code: `invalid-onupdate-property`,
message: `'onupdate' should be a function expression, not an arrow function expression` message: `'onupdate' should be a function expression, not an arrow function expression`
}); });

@ -0,0 +1,6 @@
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,10 +1,10 @@
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import nodeToString from '../../../../utils/nodeToString';
import nodeToString from '../../../utils/nodeToString'; import Component from '../../../Component';
export default function props(validator: Validator, prop: Node) { export default function props(component: Component, prop: Node) {
if (prop.value.type !== 'ArrayExpression') { if (prop.value.type !== 'ArrayExpression') {
validator.error(prop.value, { component.error(prop.value, {
code: `invalid-props-property`, code: `invalid-props-property`,
message: `'props' must be an array expression, if specified` message: `'props' must be an array expression, if specified`
}); });
@ -12,7 +12,7 @@ export default function props(validator: Validator, prop: Node) {
prop.value.elements.forEach((element: Node) => { prop.value.elements.forEach((element: Node) => {
if (typeof nodeToString(element) !== 'string') { if (typeof nodeToString(element) !== 'string') {
validator.error(element, { component.error(element, {
code: `invalid-props-property`, code: `invalid-props-property`,
message: `'props' must be an array of string literals` message: `'props' must be an array of string literals`
}); });

@ -1,13 +1,13 @@
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
const disallowed = new Set(['Literal', 'ObjectExpression', 'ArrayExpression']); const disallowed = new Set(['Literal', 'ObjectExpression', 'ArrayExpression']);
export default function setup(validator: Validator, prop: Node) { export default function setup(component: Component, prop: Node) {
while (prop.type === 'ParenthesizedExpression') prop = prop.expression; while (prop.type === 'ParenthesizedExpression') prop = prop.expression;
if (disallowed.has(prop.value.type)) { if (disallowed.has(prop.value.type)) {
validator.error(prop.value, { component.error(prop.value, {
code: `invalid-setup-property`, code: `invalid-setup-property`,
message: `'setup' must be a function` message: `'setup' must be a function`
}); });

@ -0,0 +1,6 @@
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,18 +1,18 @@
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import nodeToString from '../../../../utils/nodeToString';
import nodeToString from '../../../utils/nodeToString'; import Component from '../../../Component';
export default function tag(validator: Validator, prop: Node) { export default function tag(component: Component, prop: Node) {
const tag = nodeToString(prop.value); const tag = nodeToString(prop.value);
if (typeof tag !== 'string') { if (typeof tag !== 'string') {
validator.error(prop.value, { component.error(prop.value, {
code: `invalid-tag-property`, code: `invalid-tag-property`,
message: `'tag' must be a string literal` message: `'tag' must be a string literal`
}); });
} }
if (!/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { if (!/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
validator.error(prop.value, { component.error(prop.value, {
code: `invalid-tag-property`, code: `invalid-tag-property`,
message: `tag name must be two or more words joined by the '-' character` message: `tag name must be two or more words joined by the '-' character`
}); });

@ -1,18 +1,18 @@
import checkForDupes from '../utils/checkForDupes'; import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys'; import checkForComputedKeys from '../utils/checkForComputedKeys';
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function transitions(validator: Validator, prop: Node) { export default function transitions(component: Component, prop: Node) {
if (prop.value.type !== 'ObjectExpression') { if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, { component.error(prop, {
code: `invalid-transitions-property`, code: `invalid-transitions-property`,
message: `The 'transitions' property must be an object literal` message: `The 'transitions' property must be an object literal`
}); });
} }
checkForDupes(validator, prop.value.properties); checkForDupes(component, prop.value.properties);
checkForComputedKeys(validator, prop.value.properties); checkForComputedKeys(component, prop.value.properties);
prop.value.properties.forEach(() => { prop.value.properties.forEach(() => {
// TODO probably some validation that can happen here... // TODO probably some validation that can happen here...

@ -1,14 +1,14 @@
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function checkForAccessors( export default function checkForAccessors(
validator: Validator, component: Component,
properties: Node[], properties: Node[],
label: string label: string
) { ) {
properties.forEach(prop => { properties.forEach(prop => {
if (prop.kind !== 'init') { if (prop.kind !== 'init') {
validator.error(prop, { component.error(prop, {
code: `illegal-accessor`, code: `illegal-accessor`,
message: `${label} cannot use getters and setters` message: `${label} cannot use getters and setters`
}); });

@ -1,13 +1,13 @@
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import Component from '../../../Component';
export default function checkForComputedKeys( export default function checkForComputedKeys(
validator: Validator, component: Component,
properties: Node[] properties: Node[]
) { ) {
properties.forEach(prop => { properties.forEach(prop => {
if (prop.key.computed) { if (prop.key.computed) {
validator.error(prop, { component.error(prop, {
code: `computed-key`, code: `computed-key`,
message: `Cannot use computed keys` message: `Cannot use computed keys`
}); });

@ -1,9 +1,9 @@
import { Validator } from '../../index'; import { Node } from '../../../../interfaces';
import { Node } from '../../../interfaces'; import getName from '../../../../utils/getName';
import getName from '../../../utils/getName'; import Component from '../../../Component';
export default function checkForDupes( export default function checkForDupes(
validator: Validator, component: Component,
properties: Node[] properties: Node[]
) { ) {
const seen = new Set(); const seen = new Set();
@ -12,7 +12,7 @@ export default function checkForDupes(
const name = getName(prop.key); const name = getName(prop.key);
if (seen.has(name)) { if (seen.has(name)) {
validator.error(prop, { component.error(prop, {
code: `duplicate-property`, code: `duplicate-property`,
message: `Duplicate property '${name}'` message: `Duplicate property '${name}'`
}); });

@ -1,6 +1,6 @@
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import isReference from 'is-reference'; import isReference from 'is-reference';
import { Node } from '../../../interfaces'; import { Node } from '../../../../interfaces';
export default function usesThisOrArguments(node: Node) { export default function usesThisOrArguments(node: Node) {
let result = false; let result = false;

@ -1,8 +1,8 @@
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import Stylesheet from './Stylesheet'; import Stylesheet from './Stylesheet';
import { gatherPossibleValues, UNKNOWN } from './gatherPossibleValues'; import { gatherPossibleValues, UNKNOWN } from './gatherPossibleValues';
import { Validator } from '../validate/index';
import { Node } from '../interfaces'; import { Node } from '../interfaces';
import Component from '../compile/Component';
export default class Selector { export default class Selector {
node: Node; node: Node;
@ -97,13 +97,13 @@ export default class Selector {
}); });
} }
validate(validator: Validator) { validate(component: Component) {
this.blocks.forEach((block) => { this.blocks.forEach((block) => {
let i = block.selectors.length; let i = block.selectors.length;
while (i-- > 1) { while (i-- > 1) {
const selector = block.selectors[i]; const selector = block.selectors[i];
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') { if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
validator.error(selector, { component.error(selector, {
code: `css-invalid-global`, code: `css-invalid-global`,
message: `:global(...) must be the first element in a compound selector` message: `:global(...) must be the first element in a compound selector`
}); });
@ -124,7 +124,7 @@ export default class Selector {
for (let i = start; i < end; i += 1) { for (let i = start; i < end; i += 1) {
if (this.blocks[i].global) { if (this.blocks[i].global) {
validator.error(this.blocks[i].selectors[0], { component.error(this.blocks[i].selectors[0], {
code: `css-invalid-global`, code: `css-invalid-global`,
message: `:global(...) can be at the start or end of a selector sequence, but not in the middle` message: `:global(...) can be at the start or end of a selector sequence, but not in the middle`
}); });

@ -6,8 +6,8 @@ import getCodeFrame from '../utils/getCodeFrame';
import hash from '../utils/hash'; import hash from '../utils/hash';
import removeCSSPrefix from '../utils/removeCSSPrefix'; import removeCSSPrefix from '../utils/removeCSSPrefix';
import Element from '../compile/nodes/Element'; import Element from '../compile/nodes/Element';
import { Validator } from '../validate/index';
import { Node, Ast, Warning } from '../interfaces'; import { Node, Ast, Warning } from '../interfaces';
import Component from '../compile/Component';
const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes' const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes'
@ -78,9 +78,9 @@ class Rule {
this.declarations.forEach(declaration => declaration.transform(code, keyframes)); this.declarations.forEach(declaration => declaration.transform(code, keyframes));
} }
validate(validator: Validator) { validate(component: Component) {
this.selectors.forEach(selector => { this.selectors.forEach(selector => {
selector.validate(validator); selector.validate(component);
}); });
} }
@ -220,9 +220,9 @@ class Atrule {
}) })
} }
validate(validator: Validator) { validate(component: Component) {
this.children.forEach(child => { this.children.forEach(child => {
child.validate(validator); child.validate(component);
}); });
} }
@ -388,9 +388,9 @@ export default class Stylesheet {
}; };
} }
validate(validator: Validator) { validate(component: Component) {
this.children.forEach(child => { this.children.forEach(child => {
child.validate(validator); child.validate(component);
}); });
} }

@ -1,170 +0,0 @@
import validateJs from './js/index';
import { getLocator, Location } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame';
import Stats from '../Stats';
import error from '../utils/error';
import Stylesheet from '../css/Stylesheet';
import { Node, Ast, CompileOptions, Warning } from '../interfaces';
export class Validator {
readonly source: string;
readonly filename: string;
readonly stats: Stats;
options: CompileOptions;
locator?: (pos: number) => Location;
namespace: string;
defaultExport: Node;
properties: Map<string, Node>;
components: Map<string, Node>;
methods: Map<string, Node>;
helpers: Map<string, Node>;
animations: Map<string, Node>;
transitions: Map<string, Node>;
actions: Map<string, Node>;
slots: Set<string>;
used: {
components: Set<string>;
helpers: Set<string>;
events: Set<string>;
animations: Set<string>;
transitions: Set<string>;
actions: Set<string>;
};
constructor(ast: Ast, source: string, stats: Stats, options: CompileOptions) {
this.source = source;
this.stats = stats;
this.filename = options.filename;
this.options = options;
this.namespace = null;
this.defaultExport = null;
this.properties = new Map();
this.components = new Map();
this.methods = new Map();
this.helpers = new Map();
this.animations = new Map();
this.transitions = new Map();
this.actions = new Map();
this.slots = new Set();
this.used = {
components: new Set(),
helpers: new Set(),
events: new Set(),
animations: new Set(),
transitions: new Set(),
actions: new Set(),
};
}
error(pos: { start: number, end: number }, { code, message } : { code: string, message: string }) {
error(message, {
name: 'ValidationError',
code,
source: this.source,
start: pos.start,
end: pos.end,
filename: this.filename
});
}
warn(pos: { start: number, end: number }, { code, message }: { code: string, message: string }) {
if (!this.locator) this.locator = getLocator(this.source, { offsetLine: 1 });
const start = this.locator(pos.start);
const end = this.locator(pos.end);
const frame = getCodeFrame(this.source, start.line - 1, start.column);
this.stats.warn({
code,
message,
frame,
start,
end,
pos: pos.start,
filename: this.filename,
toString: () => `${message} (${start.line + 1}:${start.column})\n${frame}`,
});
}
}
export default function validate(
ast: Ast,
source: string,
stylesheet: Stylesheet,
stats: Stats,
options: CompileOptions
) {
const { onerror, name, filename, dev, parser } = options;
try {
if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) {
const error = new Error(`options.name must be a valid identifier (got '${name}')`);
throw error;
}
if (name && /^[a-z]/.test(name)) {
const message = `options.name should be capitalised`;
stats.warn({
code: `options-lowercase-name`,
message,
filename,
toString: () => message,
});
}
const validator = new Validator(ast, source, stats, {
name,
filename,
dev,
parser
});
if (ast.js) {
validateJs(validator, ast.js);
}
if (ast.css) {
stylesheet.validate(validator);
}
// need to do a second pass of the JS, now that we've analysed the markup
if (ast.js && validator.defaultExport) {
const categories = {
components: 'component',
// TODO helpers require a bit more work — need to analyse all expressions
// helpers: 'helper',
events: 'event definition',
transitions: 'transition',
actions: 'actions',
};
Object.keys(categories).forEach(category => {
const definitions = validator.defaultExport.declaration.properties.find(prop => prop.key.name === category);
if (definitions) {
definitions.value.properties.forEach(prop => {
const { name } = prop.key;
if (!validator.used[category].has(name)) {
validator.warn(prop, {
code: `unused-${category.slice(0, -1)}`,
message: `The '${name}' ${categories[category]} is unused`
});
}
});
}
});
}
} catch (err) {
if (onerror) {
onerror(err);
} else {
throw err;
}
}
}

@ -1,99 +0,0 @@
import propValidators from './propValidators/index';
import fuzzymatch from '../utils/fuzzymatch';
import checkForDupes from './utils/checkForDupes';
import checkForComputedKeys from './utils/checkForComputedKeys';
import namespaces from '../../utils/namespaces';
import nodeToString from '../../utils/nodeToString';
import getName from '../../utils/getName';
import { Validator } from '../';
import { Node } from '../../interfaces';
const validPropList = Object.keys(propValidators);
export default function validateJs(validator: Validator, js: Node) {
js.content.body.forEach((node: Node) => {
// check there are no named exports
if (node.type === 'ExportNamedDeclaration') {
validator.error(node, {
code: `named-export`,
message: `A component can only have a default export`
});
}
if (node.type === 'ExportDefaultDeclaration') {
if (node.declaration.type !== 'ObjectExpression') {
validator.error(node.declaration, {
code: `invalid-default-export`,
message: `Default export must be an object literal`
});
}
checkForComputedKeys(validator, node.declaration.properties);
checkForDupes(validator, node.declaration.properties);
const props = validator.properties;
node.declaration.properties.forEach((prop: Node) => {
props.set(getName(prop.key), prop);
});
// Remove these checks in version 2
if (props.has('oncreate') && props.has('onrender')) {
validator.error(props.get('onrender'), {
code: `duplicate-oncreate`,
message: 'Cannot have both oncreate and onrender'
});
}
if (props.has('ondestroy') && props.has('onteardown')) {
validator.error(props.get('onteardown'), {
code: `duplicate-ondestroy`,
message: 'Cannot have both ondestroy and onteardown'
});
}
// ensure all exported props are valid
node.declaration.properties.forEach((prop: Node) => {
const name = getName(prop.key);
const propValidator = propValidators[name];
if (propValidator) {
propValidator(validator, prop);
} else {
const match = fuzzymatch(name, validPropList);
if (match) {
validator.error(prop, {
code: `unexpected-property`,
message: `Unexpected property '${name}' (did you mean '${match}'?)`
});
} else if (/FunctionExpression/.test(prop.value.type)) {
validator.error(prop, {
code: `unexpected-property`,
message: `Unexpected property '${name}' (did you mean to include it in 'methods'?)`
});
} else {
validator.error(prop, {
code: `unexpected-property`,
message: `Unexpected property '${name}'`
});
}
}
});
if (props.has('namespace')) {
const ns = nodeToString(props.get('namespace').value);
validator.namespace = namespaces[ns] || ns;
}
validator.defaultExport = node;
}
});
['components', 'methods', 'helpers', 'transitions', 'animations', 'actions'].forEach(key => {
if (validator.properties.has(key)) {
validator.properties.get(key).value.properties.forEach((prop: Node) => {
validator[key].set(getName(prop.key), prop.value);
});
}
});
}

@ -1,16 +0,0 @@
import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys';
import { Validator } from '../../index';
import { Node } from '../../../interfaces';
export default function actions(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, {
code: `invalid-actions`,
message: `The 'actions' property must be an object literal`
});
}
checkForDupes(validator, prop.value.properties);
checkForComputedKeys(validator, prop.value.properties);
}

@ -1,16 +0,0 @@
import checkForDupes from '../utils/checkForDupes';
import checkForComputedKeys from '../utils/checkForComputedKeys';
import { Validator } from '../../index';
import { Node } from '../../../interfaces';
export default function events(validator: Validator, prop: Node) {
if (prop.value.type !== 'ObjectExpression') {
validator.error(prop, {
code: `invalid-events-property`,
message: `The 'events' property must be an object literal`
});
}
checkForDupes(validator, prop.value.properties);
checkForComputedKeys(validator, prop.value.properties);
}

@ -1,12 +0,0 @@
import oncreate from './oncreate';
import { Validator } from '../../index';
import { Node } from '../../../interfaces';
export default function onrender(validator: Validator, prop: Node) {
validator.warn(prop, {
code: `deprecated-onrender`,
message: `'onrender' has been deprecated in favour of 'oncreate', and will cause an error in Svelte 2.x`
});
oncreate(validator, prop);
}

@ -1,12 +0,0 @@
import ondestroy from './ondestroy';
import { Validator } from '../../index';
import { Node } from '../../../interfaces';
export default function onteardown(validator: Validator, prop: Node) {
validator.warn(prop, {
code: `deprecated-onteardown`,
message: `'onteardown' has been deprecated in favour of 'ondestroy', and will cause an error in Svelte 2.x`
});
ondestroy(validator, prop);
}

@ -1,6 +0,0 @@
import { Validator } from '../../index';
import { Node } from '../../../interfaces';
export default function preload(validator: Validator, prop: Node) {
// not sure there's anything we need to check here...
}

@ -1,6 +0,0 @@
import { Validator } from '../../index';
import { Node } from '../../../interfaces';
export default function store(validator: Validator, prop: Node) {
// not sure there's anything we need to check here...
}
Loading…
Cancel
Save