Repository: sveltejs/svelte (JavaScript)
Date: 2017-11-23 18:32:46+00:00
Task type: Bug Fix

Issue:
Template syntax for each breaks with reserved words as single-item variables
<!--
Thanks for raising an issue! (For *questions*, we recommend instead using https://stackoverflow.com and adding the 'svelte' tag.)

To help us help you, if you've found a bug please consider the following:

* If you can demonstrate the bug using https://svelte.technology/repl, please do.
* If that's not possible, we recommend creating a small repo that illustrates the problem.
* Make sure you include information about the browser, and which version of Svelte you're using

Reproductions should be small, self-contained, correct examples – http://sscce.org.

Occasionally, this won't be possible, and that's fine – we still appreciate you raising the issue. But please understand that Svelte is run by unpaid volunteers in their free time, and issues that follow these instructions will get fixed faster.

If you have a stack trace to include, we recommend putting inside a `<details>` block for the sake of the thread's readability:

<details>
  <summary>Stack trace</summary>

  Stack trace goes here...
</details>
-->

I ran into an issue where an `{{#each}}` in my templating code wasn't outputting values I was passing through an array.

My code: 
```
<div>
    {{#each cases as case}}
    <h2>{{case.title}}</h2>
    {{/each}}
</div>
```

The shape of my data:
```js
export default [
    {   
        title   : "One",
        options : {},
        data    : {
            
        }
    },

    {
        title   : "Two",
        options : {},
        data    : {
            
        }
    },

    {
        title   : "Three",
        options : {},
        data    : {
            
        }
    }
];
```

Looks like since `case` is a reserved word in javascript, I can't use it in my templating. 😢 
Same goes for pretty much any other reserved word, You can remove the last letter in any of the REPL examples below and see it actually render properly. 

[REPL repro with "case"](https://svelte.technology/repl?version=1.42.0&gist=e4dfd2f4b224f8c8197fec48f38a42e5)
[REPL repro with "class"](https://svelte.technology/repl?version=1.42.0&gist=feaa233dcbea1635594d2b211df08808)
[REPL repro with "default"](https://svelte.technology/repl?version=1.42.0&gist=6b6af9e80f338433eff98f27b7c5c9f1)




Issue discussion:
This is a tricky, err... case. The problem is that acorn (which parses JS on Svelte's behalf) won't parse `case.video`, because it's not actually valid JavaScript.

It's not something particular to Svelte — this would be equally impossible:

```js
cases.forEach(case => {
  console.log(case.title);
});
```

We might just have to accept that it isn't possible to have reserved words in template expressions, sadly! Unless anyone has any bright ideas?
Seems fine as is, a regular JS annoyance. Maybe if you wanted to be extra nice then in dev mode it could give a warning if any template identifier is a reversed keyword?
I must have a fundamental misunderstanding of how svelte compiles templates, because I assumed that it would be trivial for it to rename those values inside of a template. When using template tags like svelte does I tend not to think of it as "just javascript" any more, so I definitely fell into the same trap that @Morklympious did and assumed that would be fine at first.
> I assumed that it would be trivial for it to rename those values inside of a template

You can't just do a string replace (because you might have something like `{{es6 ? "let : "var"}}`) — you have to have an AST that you can traverse. But in order to generate an AST, it has to be valid JavaScript. (Unless you implement your own parser that allows illegal identifiers, but that seems... excessive 😀.)

I think @aubergene is onto the right idea — Svelte should just throw an error at compile-time that says 'you can't use `case` as an each-block context because it's a reserved word', or something that makes the issue explicit. Unfortunately that wouldn't handle the situation where `case` is a property of your data object. But it's better than nothing.
Yeah, I get why acorn doesn't like that name. I assumed that svelte used a custom parser for the special bits of template syntax (since `{{#each ...}}` and `{{#if ... }}` are clearly not valid JS) that would allow for those sorts of rewrites if invalid identifiers were used.

It's not a big deal, ultimately, and I agree that even just slightly tweaked error messaging would certainly solve most of the problem.
After some more thinking I get the issue, I wasn't thinking about later uses of `{{case}}` that are parsed by `acorn` and then explode. The `{{#each ... }}` isn't actually the problem.

Sorry for the flopping around, had to learn some more svelte before the truth dawned on me.
I think @aubergene's "Throw an error" is an acceptable solution, if only to stop people from falling down the rabbit hole of "code, what are you DOING?!". 

Solution patch:
diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts
--- a/src/parse/state/mustache.ts
+++ b/src/parse/state/mustache.ts
@@ -1,6 +1,7 @@
 import readExpression from '../read/expression';
 import { whitespace } from '../../utils/patterns';
 import { trimStart, trimEnd } from '../../utils/trim';
+import reservedNames from '../../utils/reservedNames';
 import { Parser } from '../index';
 import { Node } from '../../interfaces';
 
@@ -168,8 +169,15 @@ export default function mustache(parser: Parser) {
 
 				do {
 					parser.allowWhitespace();
+
+					const start = parser.index;
 					const destructuredContext = parser.read(validIdentifier);
+
 					if (!destructuredContext) parser.error(`Expected name`);
+					if (reservedNames.has(destructuredContext)) {
+						parser.error(`'${destructuredContext}' is a reserved word in JavaScript and cannot be used here`, start);
+					}
+
 					block.destructuredContexts.push(destructuredContext);
 					parser.allowWhitespace();
 				} while (parser.eat(','));
@@ -180,7 +188,11 @@ export default function mustache(parser: Parser) {
 				parser.allowWhitespace();
 				parser.eat(']', true);
 			} else {
-				block.context = parser.read(validIdentifier); // TODO check it's not a keyword
+				const start = parser.index;
+				block.context = parser.read(validIdentifier);
+				if (reservedNames.has(block.context)) {
+					parser.error(`'${block.context}' is a reserved word in JavaScript and cannot be used here`, start);
+				}
 
 				if (!block.context) parser.error(`Expected name`);
 			}
diff --git a/src/utils/reservedNames.ts b/src/utils/reservedNames.ts
--- a/src/utils/reservedNames.ts
+++ b/src/utils/reservedNames.ts
@@ -49,7 +49,4 @@ const reservedNames = new Set([
 	'yield',
 ]);
 
-// prevent e.g. `{{#each states as state}}` breaking
-reservedNames.add('state');
-
 export default reservedNames;


Test patch:
diff --git a/test/validator/samples/each-block-invalid-context-destructured/errors.json b/test/validator/samples/each-block-invalid-context-destructured/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/each-block-invalid-context-destructured/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "'case' is a reserved word in JavaScript and cannot be used here",
+	"loc": {
+		"line": 1,
+		"column": 18
+	},
+	"pos": 18
+}]
\ No newline at end of file
diff --git a/test/validator/samples/each-block-invalid-context-destructured/input.html b/test/validator/samples/each-block-invalid-context-destructured/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/each-block-invalid-context-destructured/input.html
@@ -0,0 +1,3 @@
+{{#each cases as [case]}}
+	{{case.title}}
+{{/each}}
\ No newline at end of file
diff --git a/test/validator/samples/each-block-invalid-context/errors.json b/test/validator/samples/each-block-invalid-context/errors.json
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/each-block-invalid-context/errors.json
@@ -0,0 +1,8 @@
+[{
+	"message": "'case' is a reserved word in JavaScript and cannot be used here",
+	"loc": {
+		"line": 1,
+		"column": 17
+	},
+	"pos": 17
+}]
\ No newline at end of file
diff --git a/test/validator/samples/each-block-invalid-context/input.html b/test/validator/samples/each-block-invalid-context/input.html
new file mode 100644
--- /dev/null
+++ b/test/validator/samples/each-block-invalid-context/input.html
@@ -0,0 +1,3 @@
+{{#each cases as case}}
+	{{case.title}}
+{{/each}}
\ No newline at end of file