Repository: sveltejs/svelte (JavaScript)
Date: 2021-07-27 14:24:43+00:00
Task type: Bug Fix

Issue:
"x was created without expected prop y" false alert with binding when prop = undefined 
https://svelte.dev/repl/65985a2e42fe4b899a379750fe609447?version=3.19.1
```html
<script>
	import Component from './Component.svelte'
	let value = undefined
</script>

<Component bind:value />
```
`<Component> was created without expected prop 'value'`

a fix for this would involve replacing a ton of test results so I'd suggest addressing #4454 at the same time


Issue discussion:
I think this is not a bug, as `let value = undefined;` is the same as `let value;`.
Maybe we should support optional props.
Same here.

This is very annoying to see warnings like these (and [these](https://github.com/sveltejs/svelte/issues/4652)).
I had this code

      export let selection;

And got these warnings

     <Component> was created without expected prop 'selection'

Changing it to 

     export let selection = "";

silenced the warnings.
@paulschreiber it's not ideal because now your code is suggesting that `selection` always expects a string, which would be incorrect self-documenting code in all but a few cases.
@oatymart what's the proper way to solve this?
Use the appropriate default according to type for each of your props that deserves one, or continue to omit it if that makes more sense. Just don't expect to achieve perfection because it's all a tradeoff between warnings, readability, and convenience.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Solution patch:
diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts
--- a/src/compiler/compile/render_dom/index.ts
+++ b/src/compiler/compile/render_dom/index.ts
@@ -121,7 +121,7 @@ export default function dom(
 	const accessors = [];
 
 	const not_equal = component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`;
-	let dev_props_check: Node[] | Node;
+	let missing_props_check: Node[] | Node;
 	let inject_state: Expression;
 	let capture_state: Expression;
 	let props_inject: Node[] | Node;
@@ -227,13 +227,13 @@ export default function dom(
 		const expected = props.filter(prop => prop.writable && !prop.initialised);
 
 		if (expected.length) {
-			dev_props_check = b`
-				const { ctx: #ctx } = this.$$;
-				const props = ${options.customElement ? x`this.attributes` : x`options.props || {}`};
-				${expected.map(prop => b`
-				if (${renderer.reference(prop.name)} === undefined && !('${prop.export_name}' in props)) {
-					@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
-				}`)}
+			missing_props_check = b`
+				$$self.$$.on_mount.push(function () {
+					${expected.map(prop => b`
+					if (${prop.name} === undefined && !(('${prop.export_name}' in $$props) || $$self.$$.bound[$$self.$$.props['${prop.export_name}']])) {
+						@_console.warn("<${component.tag}> was created without expected prop '${prop.export_name}'");
+					}`)}
+				});
 			`;
 		}
 
@@ -476,6 +476,7 @@ export default function dom(
 
 				${instance_javascript}
 
+				${missing_props_check}
 				${unknown_props_check}
 
 				${renderer.binding_groups.size > 0 && b`const $$binding_groups = [${[...renderer.binding_groups.keys()].map(_ => x`[]`)}];`}
@@ -533,8 +534,6 @@ export default function dom(
 
 					@init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, null, ${dirty});
 
-					${dev_props_check}
-
 					if (options) {
 						if (options.target) {
 							@insert(options.target, this, options.anchor);
@@ -594,8 +593,6 @@ export default function dom(
 					super(${options.dev && 'options'});
 					@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
 					${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
-
-					${dev_props_check}
 				}
 			}
 		`[0] as ClassDeclaration;


Test patch:
diff --git a/test/js/samples/capture-inject-state/expected.js b/test/js/samples/capture-inject-state/expected.js
--- a/test/js/samples/capture-inject-state/expected.js
+++ b/test/js/samples/capture-inject-state/expected.js
@@ -113,6 +113,17 @@ function instance($$self, $$props, $$invalidate) {
 	let { alias: realName } = $$props;
 	let local;
 	let shadowedByModule;
+
+	$$self.$$.on_mount.push(function () {
+		if (prop === undefined && !('prop' in $$props || $$self.$$.bound[$$self.$$.props['prop']])) {
+			console.warn("<Component> was created without expected prop 'prop'");
+		}
+
+		if (realName === undefined && !('alias' in $$props || $$self.$$.bound[$$self.$$.props['alias']])) {
+			console.warn("<Component> was created without expected prop 'alias'");
+		}
+	});
+
 	const writable_props = ['prop', 'alias'];
 
 	Object.keys($$props).forEach(key => {
@@ -166,17 +177,6 @@ class Component extends SvelteComponentDev {
 			options,
 			id: create_fragment.name
 		});
-
-		const { ctx } = this.$$;
-		const props = options.props || {};
-
-		if (/*prop*/ ctx[0] === undefined && !('prop' in props)) {
-			console.warn("<Component> was created without expected prop 'prop'");
-		}
-
-		if (/*realName*/ ctx[1] === undefined && !('alias' in props)) {
-			console.warn("<Component> was created without expected prop 'alias'");
-		}
 	}
 
 	get prop() {
diff --git a/test/js/samples/debug-empty/expected.js b/test/js/samples/debug-empty/expected.js
--- a/test/js/samples/debug-empty/expected.js
+++ b/test/js/samples/debug-empty/expected.js
@@ -72,6 +72,13 @@ function instance($$self, $$props, $$invalidate) {
 	let { $$slots: slots = {}, $$scope } = $$props;
 	validate_slots('Component', slots, []);
 	let { name } = $$props;
+
+	$$self.$$.on_mount.push(function () {
+		if (name === undefined && !('name' in $$props || $$self.$$.bound[$$self.$$.props['name']])) {
+			console.warn("<Component> was created without expected prop 'name'");
+		}
+	});
+
 	const writable_props = ['name'];
 
 	Object.keys($$props).forEach(key => {
@@ -106,13 +113,6 @@ class Component extends SvelteComponentDev {
 			options,
 			id: create_fragment.name
 		});
-
-		const { ctx } = this.$$;
-		const props = options.props || {};
-
-		if (/*name*/ ctx[0] === undefined && !('name' in props)) {
-			console.warn("<Component> was created without expected prop 'name'");
-		}
 	}
 
 	get name() {
diff --git a/test/js/samples/debug-foo-bar-baz-things/expected.js b/test/js/samples/debug-foo-bar-baz-things/expected.js
--- a/test/js/samples/debug-foo-bar-baz-things/expected.js
+++ b/test/js/samples/debug-foo-bar-baz-things/expected.js
@@ -176,6 +176,25 @@ function instance($$self, $$props, $$invalidate) {
 	let { foo } = $$props;
 	let { bar } = $$props;
 	let { baz } = $$props;
+
+	$$self.$$.on_mount.push(function () {
+		if (things === undefined && !('things' in $$props || $$self.$$.bound[$$self.$$.props['things']])) {
+			console.warn("<Component> was created without expected prop 'things'");
+		}
+
+		if (foo === undefined && !('foo' in $$props || $$self.$$.bound[$$self.$$.props['foo']])) {
+			console.warn("<Component> was created without expected prop 'foo'");
+		}
+
+		if (bar === undefined && !('bar' in $$props || $$self.$$.bound[$$self.$$.props['bar']])) {
+			console.warn("<Component> was created without expected prop 'bar'");
+		}
+
+		if (baz === undefined && !('baz' in $$props || $$self.$$.bound[$$self.$$.props['baz']])) {
+			console.warn("<Component> was created without expected prop 'baz'");
+		}
+	});
+
 	const writable_props = ['things', 'foo', 'bar', 'baz'];
 
 	Object.keys($$props).forEach(key => {
@@ -216,25 +235,6 @@ class Component extends SvelteComponentDev {
 			options,
 			id: create_fragment.name
 		});
-
-		const { ctx } = this.$$;
-		const props = options.props || {};
-
-		if (/*things*/ ctx[0] === undefined && !('things' in props)) {
-			console.warn("<Component> was created without expected prop 'things'");
-		}
-
-		if (/*foo*/ ctx[1] === undefined && !('foo' in props)) {
-			console.warn("<Component> was created without expected prop 'foo'");
-		}
-
-		if (/*bar*/ ctx[2] === undefined && !('bar' in props)) {
-			console.warn("<Component> was created without expected prop 'bar'");
-		}
-
-		if (/*baz*/ ctx[3] === undefined && !('baz' in props)) {
-			console.warn("<Component> was created without expected prop 'baz'");
-		}
 	}
 
 	get things() {
diff --git a/test/js/samples/debug-foo/expected.js b/test/js/samples/debug-foo/expected.js
--- a/test/js/samples/debug-foo/expected.js
+++ b/test/js/samples/debug-foo/expected.js
@@ -168,6 +168,17 @@ function instance($$self, $$props, $$invalidate) {
 	validate_slots('Component', slots, []);
 	let { things } = $$props;
 	let { foo } = $$props;
+
+	$$self.$$.on_mount.push(function () {
+		if (things === undefined && !('things' in $$props || $$self.$$.bound[$$self.$$.props['things']])) {
+			console.warn("<Component> was created without expected prop 'things'");
+		}
+
+		if (foo === undefined && !('foo' in $$props || $$self.$$.bound[$$self.$$.props['foo']])) {
+			console.warn("<Component> was created without expected prop 'foo'");
+		}
+	});
+
 	const writable_props = ['things', 'foo'];
 
 	Object.keys($$props).forEach(key => {
@@ -204,17 +215,6 @@ class Component extends SvelteComponentDev {
 			options,
 			id: create_fragment.name
 		});
-
-		const { ctx } = this.$$;
-		const props = options.props || {};
-
-		if (/*things*/ ctx[0] === undefined && !('things' in props)) {
-			console.warn("<Component> was created without expected prop 'things'");
-		}
-
-		if (/*foo*/ ctx[1] === undefined && !('foo' in props)) {
-			console.warn("<Component> was created without expected prop 'foo'");
-		}
 	}
 
 	get things() {
diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js
--- a/test/js/samples/dev-warning-missing-data-computed/expected.js
+++ b/test/js/samples/dev-warning-missing-data-computed/expected.js
@@ -69,6 +69,13 @@ function instance($$self, $$props, $$invalidate) {
 	validate_slots('Component', slots, []);
 	let { foo } = $$props;
 	let bar;
+
+	$$self.$$.on_mount.push(function () {
+		if (foo === undefined && !('foo' in $$props || $$self.$$.bound[$$self.$$.props['foo']])) {
+			console.warn("<Component> was created without expected prop 'foo'");
+		}
+	});
+
 	const writable_props = ['foo'];
 
 	Object.keys($$props).forEach(key => {
@@ -110,13 +117,6 @@ class Component extends SvelteComponentDev {
 			options,
 			id: create_fragment.name
 		});
-
-		const { ctx } = this.$$;
-		const props = options.props || {};
-
-		if (/*foo*/ ctx[0] === undefined && !('foo' in props)) {
-			console.warn("<Component> was created without expected prop 'foo'");
-		}
 	}
 
 	get foo() {
diff --git a/test/runtime/samples/dev-warning-missing-data-component-bind/Foo.svelte b/test/runtime/samples/dev-warning-missing-data-component-bind/Foo.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dev-warning-missing-data-component-bind/Foo.svelte
@@ -0,0 +1,8 @@
+<script>
+	export let w;
+	export let x;
+	export let y;
+	export let z = undefined;
+</script>
+
+<div>{w} {x} {y}</div>
\ No newline at end of file
diff --git a/test/runtime/samples/dev-warning-missing-data-component-bind/_config.js b/test/runtime/samples/dev-warning-missing-data-component-bind/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dev-warning-missing-data-component-bind/_config.js
@@ -0,0 +1,9 @@
+export default {
+	compileOptions: {
+		dev: true
+	},
+
+	warnings: [
+		"<Foo> was created without expected prop 'y'"
+	]
+};
diff --git a/test/runtime/samples/dev-warning-missing-data-component-bind/main.svelte b/test/runtime/samples/dev-warning-missing-data-component-bind/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/dev-warning-missing-data-component-bind/main.svelte
@@ -0,0 +1,8 @@
+<script>
+	import Foo from './Foo.svelte';
+
+	let x = undefined;
+	let w = 'w';
+</script>
+
+<Foo bind:w bind:x />
\ No newline at end of file