Repository: sveltejs/svelte (JavaScript) — Feature

Issue:
Transition validation
We need to check that

1. there are no duplication `in` or `out` directives
2. if there is a `transition` directive, there are no `in` or `out` directives
3. the transition itself is present on the `transitions` property
4. the parameters are valid (a single expression), though I think that probably already happens in parsing


Discussion:

Resolving patch:
diff --git a/src/validate/html/validateElement.js b/src/validate/html/validateElement.js
--- a/src/validate/html/validateElement.js
+++ b/src/validate/html/validateElement.js
@@ -3,6 +3,10 @@ import validateEventHandler from './validateEventHandler.js';
 export default function validateElement ( validator, node ) {
 	const isComponent = node.name === ':Self' || validator.components.has( node.name );
 
+	let hasIntro;
+	let hasOutro;
+	let hasTransition;
+
 	node.attributes.forEach( attribute => {
 		if ( !isComponent && attribute.type === 'Binding' ) {
 			const { name } = attribute;
@@ -46,9 +50,31 @@ export default function validateElement ( validator, node ) {
 			}
 		}
 
-		if ( attribute.type === 'EventHandler' ) {
+		else if ( attribute.type === 'EventHandler' ) {
 			validateEventHandler( validator, attribute );
 		}
+
+		else if ( attribute.type === 'Transition' ) {
+			const bidi = attribute.intro && attribute.outro;
+
+			if ( hasTransition ) {
+				if ( bidi ) validator.error( `An element can only have one 'transition' directive`, attribute.start );
+				validator.error( `An element cannot have both a 'transition' directive and an '${attribute.intro ? 'in' : 'out'}' directive`, attribute.start );
+			}
+
+			if ( ( hasIntro && attribute.intro ) || ( hasOutro && attribute.outro ) ) {
+				if ( bidi ) validator.error( `An element cannot have both an '${hasIntro ? 'in' : 'out'}' directive and a 'transition' directive`, attribute.start );
+				validator.error( `An element can only have one '${hasIntro ? 'in' : 'out'}' directive`, attribute.start );
+			}
+
+			if ( attribute.intro ) hasIntro = true;
+			if ( attribute.outro ) hasOutro = true;
+			if ( bidi ) hasTransition = true;
+
+			if ( !validator.transitions.has( attribute.name ) ) {
+				validator.error( `Missing transition '${attribute.name}'`, attribute.start );
+			}
+		}
 	});
 }
 
diff --git a/src/validate/index.js b/src/validate/index.js
--- a/src/validate/index.js
+++ b/src/validate/index.js
@@ -43,7 +43,8 @@ export default function validate ( parsed, source, { onerror, onwarn, name, file
 		properties: {},
 		components: new Map(),
 		methods: new Map(),
-		helpers: new Map()
+		helpers: new Map(),
+		transitions: new Map()
 	};
 
 	try {
diff --git a/src/validate/js/index.js b/src/validate/js/index.js
--- a/src/validate/js/index.js
+++ b/src/validate/js/index.js
@@ -63,7 +63,7 @@ export default function validateJs ( validator, js ) {
 		}
 	});
 
-	[ 'components', 'methods', 'helpers' ].forEach( key => {
+	[ 'components', 'methods', 'helpers', 'transitions' ].forEach( key => {
 		if ( validator.properties[ key ] ) {
 			validator.properties[ key ].value.properties.forEach( prop => {
 				validator[ key ].set( prop.key.name, prop.value );


Test patch:
diff --git a/test/validator/samples/transition-duplicate-in-transition/errors.json b/test/validator/samples/transition-duplicate-in-transition/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-in-transition/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "An element cannot have both an 'in' directive and a 'transition' directive",
+	"loc": {
+		"line": 1,
+		"column": 12
+	},
+	"pos": 12
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-in-transition/input.html b/test/validator/samples/transition-duplicate-in-transition/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-in-transition/input.html
@@ -0,0 +1,10 @@
+<div in:foo transition:bar>...</div>
+
+<script>
+	export default {
+		transitions: {
+			foo,
+			bar
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-in/errors.json b/test/validator/samples/transition-duplicate-in/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-in/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "An element can only have one 'in' directive",
+	"loc": {
+		"line": 1,
+		"column": 12
+	},
+	"pos": 12
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-in/input.html b/test/validator/samples/transition-duplicate-in/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-in/input.html
@@ -0,0 +1,10 @@
+<div in:foo in:bar>...</div>
+
+<script>
+	export default {
+		transitions: {
+			foo,
+			bar
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-out-transition/errors.json b/test/validator/samples/transition-duplicate-out-transition/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-out-transition/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "An element cannot have both an 'out' directive and a 'transition' directive",
+	"loc": {
+		"line": 1,
+		"column": 13
+	},
+	"pos": 13
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-out-transition/input.html b/test/validator/samples/transition-duplicate-out-transition/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-out-transition/input.html
@@ -0,0 +1,10 @@
+<div out:foo transition:bar>...</div>
+
+<script>
+	export default {
+		transitions: {
+			foo,
+			bar
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-out/errors.json b/test/validator/samples/transition-duplicate-out/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-out/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "An element can only have one 'out' directive",
+	"loc": {
+		"line": 1,
+		"column": 13
+	},
+	"pos": 13
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-out/input.html b/test/validator/samples/transition-duplicate-out/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-out/input.html
@@ -0,0 +1,10 @@
+<div out:foo out:bar>...</div>
+
+<script>
+	export default {
+		transitions: {
+			foo,
+			bar
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition-in/errors.json b/test/validator/samples/transition-duplicate-transition-in/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition-in/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "An element cannot have both a 'transition' directive and an 'in' directive",
+	"loc": {
+		"line": 1,
+		"column": 20
+	},
+	"pos": 20
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition-in/input.html b/test/validator/samples/transition-duplicate-transition-in/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition-in/input.html
@@ -0,0 +1,10 @@
+<div transition:foo in:bar>...</div>
+
+<script>
+	export default {
+		transitions: {
+			foo,
+			bar
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition-out/errors.json b/test/validator/samples/transition-duplicate-transition-out/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition-out/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "An element cannot have both a 'transition' directive and an 'out' directive",
+	"loc": {
+		"line": 1,
+		"column": 20
+	},
+	"pos": 20
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition-out/input.html b/test/validator/samples/transition-duplicate-transition-out/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition-out/input.html
@@ -0,0 +1,10 @@
+<div transition:foo out:bar>...</div>
+
+<script>
+	export default {
+		transitions: {
+			foo,
+			bar
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition/errors.json b/test/validator/samples/transition-duplicate-transition/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "An element can only have one 'transition' directive",
+	"loc": {
+		"line": 1,
+		"column": 20
+	},
+	"pos": 20
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition/input.html b/test/validator/samples/transition-duplicate-transition/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition/input.html
@@ -0,0 +1,10 @@
+<div transition:foo transition:bar>...</div>
+
+<script>
+	export default {
+		transitions: {
+			foo,
+			bar
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/validator/samples/transition-missing/errors.json b/test/validator/samples/transition-missing/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-missing/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "Missing transition 'foo'",
+	"loc": {
+		"line": 1,
+		"column": 5
+	},
+	"pos": 5
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-missing/input.html b/test/validator/samples/transition-missing/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/transition-missing/input.html
@@ -0,0 +1 @@
+<div in:foo>...</div>
\ No newline at end of file