Merge branch 'master' into gh-1027

pull/1053/head
Rich Harris 8 years ago committed by GitHub
commit b7d8c49dc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -731,21 +731,21 @@ export default class Generator {
if (node.type === 'Element' && (node.name === ':Component' || node.name === ':Self' || generator.components.has(node.name))) { if (node.type === 'Element' && (node.name === ':Component' || node.name === ':Self' || generator.components.has(node.name))) {
node.type = 'Component'; node.type = 'Component';
node.__proto__ = nodes.Component.prototype; Object.setPrototypeOf(node, nodes.Component.prototype);
} else if (node.name === ':Window') { // TODO do this in parse? } else if (node.name === ':Window') { // TODO do this in parse?
node.type = 'Window'; node.type = 'Window';
node.__proto__ = nodes.Window.prototype; Object.setPrototypeOf(node, nodes.Window.prototype);
} else if (node.name === ':Head') { // TODO do this in parse? } else if (node.name === ':Head') { // TODO do this in parse?
node.type = 'Head'; node.type = 'Head';
node.__proto__ = nodes.Head.prototype; Object.setPrototypeOf(node, nodes.Head.prototype);
} else if (node.type === 'Element' && node.name === 'title') { // TODO do this in parse? } else if (node.type === 'Element' && node.name === 'title') { // TODO do this in parse?
node.type = 'Title'; node.type = 'Title';
node.__proto__ = nodes.Title.prototype; Object.setPrototypeOf(node, nodes.Title.prototype);
} else if (node.type === 'Element' && node.name === 'slot' && !generator.customElement) { } else if (node.type === 'Element' && node.name === 'slot' && !generator.customElement) {
node.type = 'Slot'; node.type = 'Slot';
node.__proto__ = nodes.Slot.prototype; Object.setPrototypeOf(node, nodes.Slot.prototype);
} else if (node.type in nodes) { } else if (node.type in nodes) {
node.__proto__ = nodes[node.type].prototype; Object.setPrototypeOf(node, nodes[node.type].prototype);
} }
if (node.type === 'Element') { if (node.type === 'Element') {

@ -212,10 +212,12 @@ export default function dom(
${generator.metaBindings} ${generator.metaBindings}
${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`} ${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`}
${options.dev && ${options.dev &&
Array.from(generator.expectedProperties).map( Array.from(generator.expectedProperties).map(prop => {
prop => const message = generator.components.has(prop) ?
`if (!('${prop}' in this._state)) console.warn("${debugName} was created without expected data property '${prop}'");` `${debugName} expected to find '${prop}' in \`data\`, but found it in \`components\` instead` :
)} `${debugName} was created without expected data property '${prop}'`;
return `if (!('${prop}' in this._state)) console.warn("${message}");`
})}
${generator.bindingGroups.length && ${generator.bindingGroups.length &&
`this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`} `this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`}

@ -19,9 +19,10 @@ const elementsWithoutText = new Set([
function shouldSkip(node: Text) { function shouldSkip(node: Text) {
if (/\S/.test(node.data)) return false; if (/\S/.test(node.data)) return false;
const parentElement = node.findNearest(/(?:Element|Component)/); const parentElement = node.findNearest(/(?:Element|Component|Head)/);
if (!parentElement) return false; if (!parentElement) return false;
if (parentElement.type === 'Head') return true;
if (parentElement.type === 'Component') return parentElement.children.length === 1 && node === parentElement.children[0]; if (parentElement.type === 'Component') return parentElement.children.length === 1 && node === parentElement.children[0];
return parentElement.namespace || elementsWithoutText.has(parentElement.name); return parentElement.namespace || elementsWithoutText.has(parentElement.name);

@ -232,7 +232,7 @@ export default function ssr(
${ ${
/__missingComponent/.test(generator.renderCode) && deindent` /__missingComponent/.test(generator.renderCode) && deindent`
var __missingComponent = { var __missingComponent = {
render: () => '' _render: () => ''
}; };
` `
} }

@ -16,6 +16,10 @@ export default function validateElement(
const isComponent = const isComponent =
node.name === ':Self' || node.name === ':Component' || validator.components.has(node.name); node.name === ':Self' || node.name === ':Component' || validator.components.has(node.name);
if (isComponent) {
validator.used.components.add(node.name);
}
if (!isComponent && /^[A-Z]/.test(node.name[0])) { if (!isComponent && /^[A-Z]/.test(node.name[0])) {
// TODO upgrade to validator.error in v2 // TODO upgrade to validator.error in v2
validator.warn(`${node.name} component is not defined`, node.start); validator.warn(`${node.name} component is not defined`, node.start);
@ -152,12 +156,15 @@ export default function validateElement(
); );
} }
} else if (attribute.type === 'EventHandler') { } else if (attribute.type === 'EventHandler') {
validator.used.events.add(attribute.name);
validateEventHandler(validator, attribute, refCallees); validateEventHandler(validator, attribute, refCallees);
} else if (attribute.type === 'Transition') { } else if (attribute.type === 'Transition') {
if (isComponent) { if (isComponent) {
validator.error(`Transitions can only be applied to DOM elements, not components`, attribute.start); validator.error(`Transitions can only be applied to DOM elements, not components`, attribute.start);
} }
validator.used.transitions.add(attribute.name);
const bidi = attribute.intro && attribute.outro; const bidi = attribute.intro && attribute.outro;
if (hasTransition) { if (hasTransition) {

@ -35,6 +35,13 @@ export class Validator {
transitions: Map<string, Node>; transitions: Map<string, Node>;
slots: Set<string>; slots: Set<string>;
used: {
components: Set<string>;
helpers: Set<string>;
events: Set<string>;
transitions: Set<string>;
};
constructor(parsed: Parsed, source: string, options: CompileOptions) { constructor(parsed: Parsed, source: string, options: CompileOptions) {
this.source = source; this.source = source;
this.filename = options.filename; this.filename = options.filename;
@ -50,6 +57,13 @@ export class Validator {
this.helpers = new Map(); this.helpers = new Map();
this.transitions = new Map(); this.transitions = new Map();
this.slots = new Set(); this.slots = new Set();
this.used = {
components: new Set(),
helpers: new Set(),
events: new Set(),
transitions: new Set()
};
} }
error(message: string, pos: number) { error(message: string, pos: number) {
@ -114,6 +128,32 @@ export default function validate(
if (parsed.html) { if (parsed.html) {
validateHtml(validator, parsed.html); validateHtml(validator, parsed.html);
} }
// need to do a second pass of the JS, now that we've analysed the markup
if (parsed.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'
};
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(
`The '${name}' ${categories[category]} is unused`,
prop.start
);
}
});
}
});
}
} catch (err) { } catch (err) {
if (onerror) { if (onerror) {
onerror(err); onerror(err);

@ -6,8 +6,6 @@ const disallowed = new Set(['Literal', 'ObjectExpression', 'ArrayExpression']);
export default function data(validator: Validator, prop: Node) { export default function data(validator: Validator, prop: Node) {
while (prop.type === 'ParenthesizedExpression') prop = prop.expression; while (prop.type === 'ParenthesizedExpression') prop = prop.expression;
// TODO should we disallow references and expressions as well?
if (disallowed.has(prop.value.type)) { if (disallowed.has(prop.value.type)) {
validator.error(`'data' must be a function`, prop.value.start); validator.error(`'data' must be a function`, prop.value.start);
} }

@ -0,0 +1,231 @@
function noop() {}
function assign(target) {
var k,
source,
i = 1,
len = arguments.length;
for (; i < len; i++) {
source = arguments[i];
for (k in source) target[k] = source[k];
}
return target;
}
function appendNode(node, target) {
target.appendChild(node);
}
function detachNode(node) {
node.parentNode.removeChild(node);
}
function createElement(name) {
return document.createElement(name);
}
function blankObject() {
return Object.create(null);
}
function destroy(detach) {
this.destroy = noop;
this.fire('destroy');
this.set = this.get = noop;
if (detach !== false) this._fragment.u();
this._fragment.d();
this._fragment = this._state = null;
}
function differs(a, b) {
return a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function dispatchObservers(component, group, changed, newState, oldState) {
for (var key in group) {
if (!changed[key]) continue;
var newValue = newState[key];
var oldValue = oldState[key];
var callbacks = group[key];
if (!callbacks) continue;
for (var i = 0; i < callbacks.length; i += 1) {
var callback = callbacks[i];
if (callback.__calling) continue;
callback.__calling = true;
callback.call(component, newValue, oldValue);
callback.__calling = false;
}
}
}
function fire(eventName, data) {
var handlers =
eventName in this._handlers && this._handlers[eventName].slice();
if (!handlers) return;
for (var i = 0; i < handlers.length; i += 1) {
handlers[i].call(this, data);
}
}
function get(key) {
return key ? this._state[key] : this._state;
}
function init(component, options) {
component._observers = { pre: blankObject(), post: blankObject() };
component._handlers = blankObject();
component._bind = options._bind;
component.options = options;
component.root = options.root || component;
component.store = component.root.store || options.store;
}
function observe(key, callback, options) {
var group = options && options.defer
? this._observers.post
: this._observers.pre;
(group[key] || (group[key] = [])).push(callback);
if (!options || options.init !== false) {
callback.__calling = true;
callback.call(this, this._state[key]);
callback.__calling = false;
}
return {
cancel: function() {
var index = group[key].indexOf(callback);
if (~index) group[key].splice(index, 1);
}
};
}
function on(eventName, handler) {
if (eventName === 'teardown') return this.on('destroy', handler);
var handlers = this._handlers[eventName] || (this._handlers[eventName] = []);
handlers.push(handler);
return {
cancel: function() {
var index = handlers.indexOf(handler);
if (~index) handlers.splice(index, 1);
}
};
}
function set(newState) {
this._set(assign({}, newState));
if (this.root._lock) return;
this.root._lock = true;
callAll(this.root._beforecreate);
callAll(this.root._oncreate);
callAll(this.root._aftercreate);
this.root._lock = false;
}
function _set(newState) {
var oldState = this._state,
changed = {},
dirty = false;
for (var key in newState) {
if (differs(newState[key], oldState[key])) changed[key] = dirty = true;
}
if (!dirty) return;
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state);
if (this._bind) this._bind(changed, this._state);
if (this._fragment) {
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.p(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
}
}
function callAll(fns) {
while (fns && fns.length) fns.pop()();
}
function _mount(target, anchor) {
this._fragment.m(target, anchor);
}
function _unmount() {
if (this._fragment) this._fragment.u();
}
var proto = {
destroy: destroy,
get: get,
fire: fire,
observe: observe,
on: on,
set: set,
teardown: destroy,
_recompute: noop,
_set: _set,
_mount: _mount,
_unmount: _unmount
};
/* generated by Svelte vX.Y.Z */
function create_main_fragment(state, component) {
var meta, meta_1;
return {
c: function create() {
meta = createElement("meta");
meta_1 = createElement("meta");
this.h();
},
h: function hydrate() {
meta.name = "twitter:creator";
meta.content = "@sveltejs";
meta_1.name = "twitter:title";
meta_1.content = "Svelte";
},
m: function mount(target, anchor) {
appendNode(meta, document.head);
appendNode(meta_1, document.head);
},
p: noop,
u: function unmount() {
detachNode(meta);
detachNode(meta_1);
},
d: noop
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);
this._fragment = create_main_fragment(this._state, this);
if (options.target) {
this._fragment.c();
this._fragment.m(options.target, options.anchor || null);
}
}
assign(SvelteComponent.prototype, proto);
export default SvelteComponent;

@ -0,0 +1,50 @@
/* generated by Svelte vX.Y.Z */
import { appendNode, assign, createElement, detachNode, init, noop, proto } from "svelte/shared.js";
function create_main_fragment(state, component) {
var meta, meta_1;
return {
c: function create() {
meta = createElement("meta");
meta_1 = createElement("meta");
this.h();
},
h: function hydrate() {
meta.name = "twitter:creator";
meta.content = "@sveltejs";
meta_1.name = "twitter:title";
meta_1.content = "Svelte";
},
m: function mount(target, anchor) {
appendNode(meta, document.head);
appendNode(meta_1, document.head);
},
p: noop,
u: function unmount() {
detachNode(meta);
detachNode(meta_1);
},
d: noop
};
}
function SvelteComponent(options) {
init(this, options);
this._state = assign({}, options.data);
this._fragment = create_main_fragment(this._state, this);
if (options.target) {
this._fragment.c();
this._fragment.m(options.target, options.anchor || null);
}
}
assign(SvelteComponent.prototype, proto);
export default SvelteComponent;

@ -0,0 +1,4 @@
<:Head>
<meta name='twitter:creator' content='@sveltejs'>
<meta name='twitter:title' content='Svelte'>
</:Head>

@ -0,0 +1,14 @@
export default {
dev: true,
data: {
x: true
},
html: '',
warnings: [
`<Main$> expected to find 'Foo' in \`data\`, but found it in \`components\` instead`,
`<Main$> expected to find 'Bar' in \`data\`, but found it in \`components\` instead`
]
};

@ -0,0 +1,13 @@
<:Component {x ? Foo : Bar}/>
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
export default {
components: {
Foo,
Bar
}
};
</script>

@ -1,4 +1,4 @@
<div></div> <foo></foo>
<script> <script>
export default { export default {

@ -0,0 +1,11 @@
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
export default {
components: {
Foo,
Bar
}
};
</script>

@ -0,0 +1,18 @@
[
{
"message": "The 'Foo' component is unused",
"loc": {
"line": 7,
"column": 3
},
"pos": 109
},
{
"message": "The 'Bar' component is unused",
"loc": {
"line": 8,
"column": 3
},
"pos": 117
}
]

@ -0,0 +1,9 @@
<script>
export default {
events: {
drag: (node, callback) => {
// implementation goes here
}
}
};
</script>

@ -0,0 +1,8 @@
[{
"message": "The 'drag' event definition is unused",
"loc": {
"line": 4,
"column": 3
},
"pos": 42
}]

@ -0,0 +1,7 @@
<script>
export default {
helpers: {
uppercase: x => x.toUpperCase()
}
};
</script>

@ -0,0 +1,8 @@
[{
"message": "The 'uppercase' helper is unused",
"loc": {
"line": 4,
"column": 3
},
"pos": 43
}]

@ -0,0 +1,9 @@
<script>
export default {
transitions: {
spin: node => {
// implementation goes here
}
}
};
</script>

@ -0,0 +1,8 @@
[{
"message": "The 'spin' transition is unused",
"loc": {
"line": 4,
"column": 3
},
"pos": 47
}]
Loading…
Cancel
Save