warn on missing data

pull/1864/head
Rich Harris 7 years ago
parent 58e532fc08
commit de0830b0c5

@ -15,6 +15,7 @@ import { Node, Ast, CompileOptions, CustomElementOptions } from '../interfaces';
import error from '../utils/error';
import getCodeFrame from '../utils/getCodeFrame';
import flattenReference from '../utils/flattenReference';
import addToSet from '../utils/addToSet';
// We need to tell estree-walker that it should always
// look for an `else` block, otherwise it might get
@ -44,31 +45,32 @@ export default class Component {
properties: Map<string, Node>;
imports: Node[];
imports: Node[] = [];
namespace: string;
hasComponents: boolean;
javascript: string;
declarations: string[];
exports: Array<{ name: string, as: string }>;
event_handlers: Array<{ name: string, body: string }>;
props: string[];
declarations: string[] = [];
writable_declarations: Set<string> = new Set();
initialised_declarations: Set<string> = new Set();
exports: Array<{ name: string, as: string }> = [];
event_handlers: Array<{ name: string, body: string }> = [];
code: MagicString;
indirectDependencies: Map<string, Set<string>>;
expectedProperties: Set<string>;
refs: Set<string>;
indirectDependencies: Map<string, Set<string>> = new Map();
expectedProperties: Set<string> = new Set();
refs: Set<string> = new Set();
file: string;
locate: (c: number) => { line: number, column: number };
stylesheet: Stylesheet;
userVars: Set<string>;
templateVars: Map<string, string>;
aliases: Map<string, string>;
usedNames: Set<string>;
userVars: Set<string> = new Set();
templateVars: Map<string, string> = new Map();
aliases: Map<string, string> = new Map();
usedNames: Set<string> = new Set();
init_uses_self = false;
locator: (search: number, startIndex?: number) => {
@ -89,38 +91,17 @@ export default class Component {
this.source = source;
this.options = options;
this.imports = [];
this.declarations = [];
this.exports = [];
this.event_handlers = [];
this.refs = new Set();
this.indirectDependencies = new Map();
this.file = options.filename && (
typeof process !== 'undefined' ? options.filename.replace(process.cwd(), '').replace(/^[\/\\]/, '') : options.filename
);
this.locate = getLocator(this.source);
// track which properties are needed, so we can provide useful info
// in dev mode
this.expectedProperties = new Set();
this.code = new MagicString(source);
// styles
this.stylesheet = new Stylesheet(source, ast, options.filename, options.dev);
this.stylesheet.validate(this);
// allow compiler to deconflict user's `import { flush } from 'whatever'` and
// Svelte's builtin `import { flush, ... } from 'svelte/internal.ts'`;
this.userVars = new Set();
this.templateVars = new Map();
this.aliases = new Map();
this.usedNames = new Set();
this.properties = new Map();
this.walkJs();
@ -150,6 +131,7 @@ export default class Component {
if (!this.ast.js) {
this.declarations = Array.from(this.expectedProperties);
addToSet(this.writable_declarations, this.expectedProperties);
this.exports = this.declarations.map(name => ({
name,
@ -390,6 +372,9 @@ export default class Component {
this.declarations.push(name);
});
this.writable_declarations = scope.writable_declarations;
this.initialised_declarations = scope.initialised_declarations;
globals.forEach(name => {
this.userVars.add(name);
});
@ -401,7 +386,7 @@ export default class Component {
this.error(node, {
code: `default-export`,
message: `A component cannot have a default export`
})
});
}
if (node.type === 'ExportNamedDeclaration') {

@ -61,7 +61,8 @@ export default function dom(
const globals = expectedProperties.filter(prop => globalWhitelist.has(prop));
if (component.customElement) {
const props = component.props || Array.from(component.expectedProperties);
// TODO use `export` to determine this
const props = Array.from(component.expectedProperties);
builder.addBlock(deindent`
class ${name} extends HTMLElement {
@ -118,8 +119,8 @@ export default function dom(
);
}
builder.addBlock(deindent`
class ${name} extends ${superclass} {
const body = [
deindent`
$$init($$make_dirty) {
${component.init_uses_self && `const $$self = this;`}
${component.javascript || component.exports.map(x => `let ${x.name};`)}
@ -145,17 +146,59 @@ export default function dom(
$$create_fragment(${component.alias('component')}, ctx) {
${block.getContents()}
}
`
];
if (component.options.dev) {
// TODO check no uunexpected props were passed, as well as
// checking that expected ones were passed
const expected = component.exports
.map(x => x.name)
.filter(name => !component.initialised_declarations.has(name));
if (expected.length) {
const debug_name = `<${component.customElement ? component.tag : name}>`;
body.push(deindent`
$$checkProps() {
const state = this.$$.get_state();
${expected.map(name => deindent`
${component.exports.map(x => deindent`
if (state.${name} === undefined) {
console.warn("${debug_name} was created without expected data property '${name}'");
}
`)}
}
`);
}
}
component.exports.forEach(x => {
body.push(deindent`
get ${x.as}() {
return this.$$.get_state().${x.name};
}
`);
if (component.writable_declarations.has(x.as)) {
body.push(deindent`
set ${x.as}(value) {
this.$set({ ${x.name}: value });
@flush();
}
`)}
`);
} else if (component.options.dev) {
body.push(deindent`
set ${x.as}(value) {
throw new Error("${x.as} is read-only");
}
`);
}
});
builder.addBlock(deindent`
class ${name} extends ${superclass} {
${body.join('\n\n')}
}
`);
}

@ -116,6 +116,7 @@ export class $$ComponentDev extends $$Component {
}
super(options);
this.$$checkProps();
}
$destroy() {
@ -124,4 +125,8 @@ export class $$ComponentDev extends $$Component {
console.warn(`Component was already destroyed`);
};
}
$$checkProps() {
// noop by default
}
}

@ -54,21 +54,28 @@ export function createScopes(expression: Node) {
export class Scope {
parent: Scope;
block: boolean;
declarations: Set<string>;
declarations: Set<string> = new Set();
writable_declarations: Set<string> = new Set();
initialised_declarations: Set<string> = new Set();
constructor(parent: Scope, block: boolean) {
this.parent = parent;
this.block = block;
this.declarations = new Set();
}
addDeclaration(node: Node) {
if (node.kind === 'var' && !this.block && this.parent) {
this.parent.addDeclaration(node);
} else if (node.type === 'VariableDeclaration') {
const writable = node.kind !== 'const';
const initialised = !!node.init;
node.declarations.forEach((declarator: Node) => {
extractNames(declarator.id).forEach(name => {
this.declarations.add(name);
if (writable) this.writable_declarations.add(name);
if (initialised) this.initialised_declarations.add(name);
});
});
} else {

@ -1,4 +1,6 @@
export default {
solo: 1,
compileOptions: {
dev: true
},

Loading…
Cancel
Save