Repository: sveltejs/svelte (JavaScript)
Date: 2018-01-16 19:42:30+00:00
Task type: Bug Fix

Issue:
Ignore a11y checks on child components
I just encountered an interesting "bug" of the a11y implementation. I have a component that uses scope as the prop name, and I get a warning that The scope attribute should only be used with <th> elements. I feel that a11y should be disabled on component definitions. Here's the full error (including code)

```
src/Components/Foo/index.html (20:71) A11y: The scope attribute should only be used with <th> elements
18:   <div id="foo-id">
19:     <div class="foo-class">
20:       <Child scope="local"></Child>
                 ^
21:     </div>
22:   </div>
```


Issue discussion:
At least until Svelte v2, we are merely suggesting and not enforcing that component names begin with a capital letter and regular elements begin with a lowercase letter. So we have to check whether the given element is actually intended as a component before skipping the a11y checks. `validateElement.ts` has a check for this near the top (`node.name === ':Self' || node.name === ':Component' || validator.components.has(node.name)`) which perhaps should be copied into `a11y.ts`, or perhaps which should be extracted into some utility function, or perhaps we should refactor that check out into `validateHtml` in `index.ts` somehow.

Solution patch:
diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts
--- a/src/validate/html/index.ts
+++ b/src/validate/html/index.ts
@@ -19,14 +19,29 @@ export default function validateHtml(validator: Validator, html: Node) {
 	const elementStack: Node[] = [];
 
 	function visit(node: Node) {
-		a11y(validator, node, elementStack);
-
 		if (node.type === 'Element') {
 			if (meta.has(node.name)) {
 				return meta.get(node.name)(validator, node, refs, refCallees);
 			}
 
-			validateElement(validator, node, refs, refCallees, stack, elementStack);
+			const isComponent =
+				node.name === ':Self' ||
+				node.name === ':Component' ||
+				validator.components.has(node.name);
+
+			validateElement(
+				validator,
+				node,
+				refs,
+				refCallees,
+				stack,
+				elementStack,
+				isComponent
+			);
+
+			if (!isComponent) {
+				a11y(validator, node, elementStack);
+			}
 		} else if (node.type === 'EachBlock') {
 			if (validator.helpers.has(node.context)) {
 				let c = node.expression.end;
diff --git a/src/validate/html/validateElement.ts b/src/validate/html/validateElement.ts
--- a/src/validate/html/validateElement.ts
+++ b/src/validate/html/validateElement.ts
@@ -11,11 +11,9 @@ export default function validateElement(
 	refs: Map<string, Node[]>,
 	refCallees: Node[],
 	stack: Node[],
-	elementStack: Node[]
+	elementStack: Node[],
+	isComponent: Boolean
 ) {
-	const isComponent =
-		node.name === ':Self' || node.name === ':Component' || validator.components.has(node.name);
-
 	if (isComponent) {
 		validator.used.components.add(node.name);
 	}
diff --git a/src/validate/html/validateHead.ts b/src/validate/html/validateHead.ts
--- a/src/validate/html/validateHead.ts
+++ b/src/validate/html/validateHead.ts
@@ -11,6 +11,6 @@ export default function validateHead(validator: Validator, node: Node, refs: Map
 
 	node.children.forEach(node => {
 		if (node.type !== 'Element') return; // TODO handle {{#if}} and friends?
-		validateElement(validator, node, refs, refCallees, [], []);
+		validateElement(validator, node, refs, refCallees, [], [], false);
 	});
 }


Test patch:
diff --git a/test/validator/samples/a11y-not-on-components/input.html b/test/validator/samples/a11y-not-on-components/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/a11y-not-on-components/input.html
@@ -0,0 +1,9 @@
+<Widget scope="foo">
+	<input autofocus>
+</Widget>
+
+<script>
+	export default {
+		components: { Widget },
+	};
+</script>
diff --git a/test/validator/samples/a11y-not-on-components/warnings.json b/test/validator/samples/a11y-not-on-components/warnings.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/a11y-not-on-components/warnings.json
@@ -0,0 +1,10 @@
+[
+	{
+		"message": "A11y: Avoid using autofocus",
+		"loc": {
+			"column": 8,
+			"line": 2
+		},
+		"pos": 29
+	}
+]