Repository: sveltejs/svelte (JavaScript)
Date: 2020-08-29 06:11:49+00:00
Task type: Bug Fix

Issue:
A11y: A form label must be associated with a control. (When label actually has control element)
Case when labelable element is not direct child of label element triggers current warning

https://svelte.dev/repl/418bcf45f55741e49361839687cf11fa?version=3.29.0

Expected behavior: compiler recursively checks if label has labelable element

Additional context:
I started to write recursive function to get this warning working properly but then I found https://github.com/sveltejs/svelte/pull/5323 PR that covers my issue



Issue discussion:

Solution patch:
diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts
--- a/src/compiler/compile/nodes/Element.ts
+++ b/src/compiler/compile/nodes/Element.ts
@@ -616,8 +616,24 @@ export default class Element extends Node {
 		}
 
 		if (this.name === 'label') {
-			const has_input_child = this.children.some(i => (i instanceof Element && a11y_labelable.has(i.name) ));
-			if (!attribute_map.has('for') && !has_input_child) {
+			const has_input_child = (children: INode[]) => {
+				if (children.some(child => (child instanceof Element && (a11y_labelable.has(child.name) || child.name === 'slot')))) {
+					return true;
+				}
+
+				for (const child of children) {
+					if (!('children' in child) || child.children.length === 0) {
+						continue;
+					}
+					if (has_input_child(child.children)) {
+						return true;
+					}
+				}
+
+				return false;
+			};
+
+			if (!attribute_map.has('for') && !has_input_child(this.children)) {
 				component.warn(this, compiler_warnings.a11y_label_has_associated_control);
 			}
 		}


Test patch:
diff --git a/test/validator/samples/a11y-label-has-associated-control-2/input.svelte b/test/validator/samples/a11y-label-has-associated-control-2/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/a11y-label-has-associated-control-2/input.svelte
@@ -0,0 +1,3 @@
+<label>
+  <slot />
+</label>
diff --git a/test/validator/samples/a11y-label-has-associated-control-2/warnings.json b/test/validator/samples/a11y-label-has-associated-control-2/warnings.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/a11y-label-has-associated-control-2/warnings.json
@@ -0,0 +1 @@
+[]
diff --git a/test/validator/samples/a11y-label-has-associated-control/input.svelte b/test/validator/samples/a11y-label-has-associated-control/input.svelte
--- a/test/validator/samples/a11y-label-has-associated-control/input.svelte
+++ b/test/validator/samples/a11y-label-has-associated-control/input.svelte
@@ -1,6 +1,12 @@
+<script>
+  import LabelComponent from './label.svelte'
+</script>
+
 <label>A</label>
 <label for="id">B</label>
 
 <label>C <input type="text" /></label>
 <label>D <button>D</button></label>
 <label>E <span></span></label>
+<label>F {#if true}<input type="text" />{/if}</label>
+<LabelComponent>G <input type="text" /></LabelComponent>
diff --git a/test/validator/samples/a11y-label-has-associated-control/warnings.json b/test/validator/samples/a11y-label-has-associated-control/warnings.json
--- a/test/validator/samples/a11y-label-has-associated-control/warnings.json
+++ b/test/validator/samples/a11y-label-has-associated-control/warnings.json
@@ -1,32 +1,32 @@
 [
-  {
-    "code": "a11y-label-has-associated-control",
-    "end": {
-      "character": 16,
-      "column": 16,
-      "line": 1
-    },
-    "message": "A11y: A form label must be associated with a control.",
-    "pos": 0,
-    "start": {
-      "character": 0,
-      "column": 0,
-      "line": 1
-    }
-  },
-  {
-    "code": "a11y-label-has-associated-control",
-    "end": {
-      "character": 149,
-      "column": 30,
-      "line": 6
-    },
-    "message": "A11y: A form label must be associated with a control.",
-    "pos": 119,
-    "start": {
-      "character": 119,
-      "column": 0,
-      "line": 6
-    }
-  }
+	{
+		"code": "a11y-label-has-associated-control",
+		"end": {
+			"character": 82,
+			"column": 16,
+			"line": 5
+		},
+		"message": "A11y: A form label must be associated with a control.",
+		"pos": 66,
+		"start": {
+			"character": 66,
+			"column": 0,
+			"line": 5
+		}
+	},
+	{
+		"code": "a11y-label-has-associated-control",
+		"end": {
+			"character": 215,
+			"column": 30,
+			"line": 10
+		},
+		"message": "A11y: A form label must be associated with a control.",
+		"pos": 185,
+		"start": {
+			"character": 185,
+			"column": 0,
+			"line": 10
+		}
+	}
 ]