diff --git a/CHANGELOG.md b/CHANGELOG.md index 6337653fb1..16e6484ab3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ * Improve parsing error messages when there is a pending unclosed tag ([#4131](https://github.com/sveltejs/svelte/issues/4131)) * Disallow attribute/prop names from matching two-way-bound names or `{shorthand}` attribute/prop names ([#4325](https://github.com/sveltejs/svelte/issues/4325)) * Improve performance of `flush()` by not using `.shift()` ([#4356](https://github.com/sveltejs/svelte/pull/4356)) +* Permit reserved keywords as destructuring keys in `{#each}` ([#4372](https://github.com/sveltejs/svelte/issues/4372)) +* Disallow reserved keywords in `{expressions}` ([#4372](https://github.com/sveltejs/svelte/issues/4372)) * Fix code generation error with precedence of arrow functions ([#4384](https://github.com/sveltejs/svelte/issues/4384)) ## 3.18.1 diff --git a/src/compiler/parse/index.ts b/src/compiler/parse/index.ts index e552374698..a809eeebeb 100644 --- a/src/compiler/parse/index.ts +++ b/src/compiler/parse/index.ts @@ -141,7 +141,7 @@ export class Parser { return result; } - read_identifier() { + read_identifier(allow_reserved = false) { const start = this.index; let i = this.index; @@ -160,7 +160,7 @@ export class Parser { const identifier = this.template.slice(this.index, this.index = i); - if (reserved.has(identifier)) { + if (!allow_reserved && reserved.has(identifier)) { this.error({ code: `unexpected-reserved-word`, message: `'${identifier}' is a reserved word in JavaScript and cannot be used here` diff --git a/src/compiler/parse/read/context.ts b/src/compiler/parse/read/context.ts index 0db0c676a3..fe666467f8 100644 --- a/src/compiler/parse/read/context.ts +++ b/src/compiler/parse/read/context.ts @@ -1,4 +1,5 @@ import { Parser } from '../index'; +import { reserved } from '../../utils/names'; interface Identifier { start: number; @@ -116,8 +117,11 @@ export default function read_context(parser: Parser) { break; } + // TODO: DRY this out somehow + // We don't know whether we want to allow reserved words until we see whether there's a ':' after it + // Probably ideally we'd use Acorn to do all of this const start = parser.index; - const name = parser.read_identifier(); + const name = parser.read_identifier(true); const key: Identifier = { start, end: parser.index, @@ -126,9 +130,19 @@ export default function read_context(parser: Parser) { }; parser.allow_whitespace(); - const value = parser.eat(':') - ? (parser.allow_whitespace(), read_context(parser)) - : key; + let value: Context; + if (parser.eat(':')) { + parser.allow_whitespace(); + value = read_context(parser); + } else { + if (reserved.has(name)) { + parser.error({ + code: `unexpected-reserved-word`, + message: `'${name}' is a reserved word in JavaScript and cannot be used here` + }, start); + } + value = key; + } const property: Property = { start, diff --git a/src/compiler/parse/read/expression.ts b/src/compiler/parse/read/expression.ts index c8e955b719..f0f4e15c7a 100644 --- a/src/compiler/parse/read/expression.ts +++ b/src/compiler/parse/read/expression.ts @@ -1,37 +1,9 @@ import { parse_expression_at } from '../acorn'; import { Parser } from '../index'; -import { Identifier, Node, SimpleLiteral } from 'estree'; +import { Node } from 'estree'; import { whitespace } from '../../utils/patterns'; -const literals = new Map([['true', true], ['false', false], ['null', null]]); - export default function read_expression(parser: Parser): Node { - const start = parser.index; - - const name = parser.read_until(/\s*}/); - if (name && /^[a-z]+$/.test(name)) { - const end = start + name.length; - - if (literals.has(name)) { - return { - type: 'Literal', - start, - end, - value: literals.get(name), - raw: name, - } as SimpleLiteral; - } - - return { - type: 'Identifier', - start, - end: start + name.length, - name, - } as Identifier; - } - - parser.index = start; - try { const node = parse_expression_at(parser.template, parser.index); diff --git a/test/parser/samples/action-with-identifier/output.json b/test/parser/samples/action-with-identifier/output.json index 435aee4444..9ae0011c52 100644 --- a/test/parser/samples/action-with-identifier/output.json +++ b/test/parser/samples/action-with-identifier/output.json @@ -20,6 +20,16 @@ "type": "Identifier", "start": 20, "end": 27, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 27 + } + }, "name": "message" } } diff --git a/test/parser/samples/attribute-dynamic-boolean/output.json b/test/parser/samples/attribute-dynamic-boolean/output.json index 478144152a..ab6b3927ce 100644 --- a/test/parser/samples/attribute-dynamic-boolean/output.json +++ b/test/parser/samples/attribute-dynamic-boolean/output.json @@ -24,6 +24,16 @@ "type": "Identifier", "start": 20, "end": 28, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 28 + } + }, "name": "readonly" } } diff --git a/test/parser/samples/attribute-dynamic-reserved/input.svelte b/test/parser/samples/attribute-dynamic-reserved/input.svelte deleted file mode 100644 index d973a9dea0..0000000000 --- a/test/parser/samples/attribute-dynamic-reserved/input.svelte +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic-reserved/output.json b/test/parser/samples/attribute-dynamic-reserved/output.json deleted file mode 100644 index 22be3c9087..0000000000 --- a/test/parser/samples/attribute-dynamic-reserved/output.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "html": { - "start": 0, - "end": 25, - "type": "Fragment", - "children": [ - { - "start": 0, - "end": 25, - "type": "Element", - "name": "div", - "attributes": [ - { - "start": 5, - "end": 18, - "type": "Attribute", - "name": "class", - "value": [ - { - "start": 11, - "end": 18, - "type": "MustacheTag", - "expression": { - "type": "Identifier", - "start": 12, - "end": 17, - "name": "class" - } - } - ] - } - ], - "children": [] - } - ] - } -} \ No newline at end of file diff --git a/test/parser/samples/attribute-dynamic/output.json b/test/parser/samples/attribute-dynamic/output.json index a48f376876..28b9da1dea 100644 --- a/test/parser/samples/attribute-dynamic/output.json +++ b/test/parser/samples/attribute-dynamic/output.json @@ -31,6 +31,16 @@ "type": "Identifier", "start": 20, "end": 25, + "loc": { + "start": { + "line": 1, + "column": 20 + }, + "end": { + "line": 1, + "column": 25 + } + }, "name": "color" } }, @@ -53,6 +63,16 @@ "type": "Identifier", "start": 30, "end": 35, + "loc": { + "start": { + "line": 1, + "column": 30 + }, + "end": { + "line": 1, + "column": 35 + } + }, "name": "color" } } diff --git a/test/parser/samples/attribute-with-whitespace/output.json b/test/parser/samples/attribute-with-whitespace/output.json index 11d5aa5e06..edf05ca32e 100644 --- a/test/parser/samples/attribute-with-whitespace/output.json +++ b/test/parser/samples/attribute-with-whitespace/output.json @@ -20,6 +20,16 @@ "type": "Identifier", "start": 19, "end": 22, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 22 + } + }, "name": "foo" } } diff --git a/test/parser/samples/binding/output.json b/test/parser/samples/binding/output.json index 558215f41a..c980b7f3eb 100644 --- a/test/parser/samples/binding/output.json +++ b/test/parser/samples/binding/output.json @@ -27,6 +27,16 @@ "type": "Identifier", "start": 50, "end": 54, + "loc": { + "start": { + "line": 5, + "column": 19 + }, + "end": { + "line": 5, + "column": 23 + } + }, "name": "name" } } diff --git a/test/parser/samples/each-block-destructured/output.json b/test/parser/samples/each-block-destructured/output.json index bafde1324d..425c609a2c 100644 --- a/test/parser/samples/each-block-destructured/output.json +++ b/test/parser/samples/each-block-destructured/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 46, "end": 49, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 8 + } + }, "name": "key" } }, @@ -58,6 +68,16 @@ "type": "Identifier", "start": 53, "end": 58, + "loc": { + "start": { + "line": 2, + "column": 12 + }, + "end": { + "line": 2, + "column": 17 + } + }, "name": "value" } } diff --git a/test/parser/samples/each-block-else/output.json b/test/parser/samples/each-block-else/output.json index 19b5af19b4..2720ce5292 100644 --- a/test/parser/samples/each-block-else/output.json +++ b/test/parser/samples/each-block-else/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 31, "end": 37, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 11 + } + }, "name": "animal" } } diff --git a/test/parser/samples/each-block-indexed/output.json b/test/parser/samples/each-block-indexed/output.json index 68a12ea31c..50f2000a36 100644 --- a/test/parser/samples/each-block-indexed/output.json +++ b/test/parser/samples/each-block-indexed/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 34, "end": 35, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 6 + } + }, "name": "i" } }, @@ -58,6 +68,16 @@ "type": "Identifier", "start": 39, "end": 45, + "loc": { + "start": { + "line": 2, + "column": 10 + }, + "end": { + "line": 2, + "column": 16 + } + }, "name": "animal" } } diff --git a/test/parser/samples/each-block-keyed/output.json b/test/parser/samples/each-block-keyed/output.json index 61b5442c0d..7dc8681453 100644 --- a/test/parser/samples/each-block-keyed/output.json +++ b/test/parser/samples/each-block-keyed/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 37, "end": 41, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 9 + } + }, "name": "todo" } } diff --git a/test/parser/samples/each-block/output.json b/test/parser/samples/each-block/output.json index 38bc7e1d04..6594fb50a6 100644 --- a/test/parser/samples/each-block/output.json +++ b/test/parser/samples/each-block/output.json @@ -40,6 +40,16 @@ "type": "Identifier", "start": 31, "end": 37, + "loc": { + "start": { + "line": 2, + "column": 5 + }, + "end": { + "line": 2, + "column": 11 + } + }, "name": "animal" } } diff --git a/test/parser/samples/element-with-mustache/output.json b/test/parser/samples/element-with-mustache/output.json index f7c2e9936b..049e373100 100644 --- a/test/parser/samples/element-with-mustache/output.json +++ b/test/parser/samples/element-with-mustache/output.json @@ -26,6 +26,16 @@ "type": "Identifier", "start": 11, "end": 15, + "loc": { + "start": { + "line": 1, + "column": 11 + }, + "end": { + "line": 1, + "column": 15 + } + }, "name": "name" } }, diff --git a/test/parser/samples/event-handler/output.json b/test/parser/samples/event-handler/output.json index 44bb83de7d..95db9998f4 100644 --- a/test/parser/samples/event-handler/output.json +++ b/test/parser/samples/event-handler/output.json @@ -128,6 +128,16 @@ "type": "Identifier", "start": 68, "end": 75, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 12 + } + }, "name": "visible" }, "children": [ diff --git a/test/parser/samples/if-block-else/output.json b/test/parser/samples/if-block-else/output.json index 8b22cfa26b..1f2cff3ff9 100644 --- a/test/parser/samples/if-block-else/output.json +++ b/test/parser/samples/if-block-else/output.json @@ -12,6 +12,16 @@ "type": "Identifier", "start": 5, "end": 8, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 8 + } + }, "name": "foo" }, "children": [ diff --git a/test/parser/samples/if-block/output.json b/test/parser/samples/if-block/output.json index 1714bb7141..ef895a366a 100644 --- a/test/parser/samples/if-block/output.json +++ b/test/parser/samples/if-block/output.json @@ -12,6 +12,16 @@ "type": "Identifier", "start": 5, "end": 8, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 8 + } + }, "name": "foo" }, "children": [ diff --git a/test/parser/samples/implicitly-closed-li-block/output.json b/test/parser/samples/implicitly-closed-li-block/output.json index 4a75a34223..767f64053c 100644 --- a/test/parser/samples/implicitly-closed-li-block/output.json +++ b/test/parser/samples/implicitly-closed-li-block/output.json @@ -40,6 +40,16 @@ "type": "Literal", "start": 18, "end": 22, + "loc": { + "start": { + "line": 3, + "column": 6 + }, + "end": { + "line": 3, + "column": 10 + } + }, "value": true, "raw": "true" }, diff --git a/test/parser/samples/refs/output.json b/test/parser/samples/refs/output.json index 72a3d7689c..fb94ffd701 100644 --- a/test/parser/samples/refs/output.json +++ b/test/parser/samples/refs/output.json @@ -27,6 +27,16 @@ "type": "Identifier", "start": 49, "end": 52, + "loc": { + "start": { + "line": 5, + "column": 19 + }, + "end": { + "line": 5, + "column": 22 + } + }, "name": "foo" } } diff --git a/test/parser/samples/script-comment-trailing-multiline/output.json b/test/parser/samples/script-comment-trailing-multiline/output.json index be83eeea8b..184a29978d 100644 --- a/test/parser/samples/script-comment-trailing-multiline/output.json +++ b/test/parser/samples/script-comment-trailing-multiline/output.json @@ -33,6 +33,16 @@ "type": "Identifier", "start": 90, "end": 94, + "loc": { + "start": { + "line": 9, + "column": 11 + }, + "end": { + "line": 9, + "column": 15 + } + }, "name": "name" } }, diff --git a/test/parser/samples/script-comment-trailing/output.json b/test/parser/samples/script-comment-trailing/output.json index a2bc00bc00..2aca23f667 100644 --- a/test/parser/samples/script-comment-trailing/output.json +++ b/test/parser/samples/script-comment-trailing/output.json @@ -33,6 +33,16 @@ "type": "Identifier", "start": 79, "end": 83, + "loc": { + "start": { + "line": 7, + "column": 11 + }, + "end": { + "line": 7, + "column": 15 + } + }, "name": "name" } }, diff --git a/test/parser/samples/script/output.json b/test/parser/samples/script/output.json index 00b7073a19..4d0aa9cf36 100644 --- a/test/parser/samples/script/output.json +++ b/test/parser/samples/script/output.json @@ -33,6 +33,16 @@ "type": "Identifier", "start": 52, "end": 56, + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 15 + } + }, "name": "name" } }, diff --git a/test/parser/samples/space-between-mustaches/output.json b/test/parser/samples/space-between-mustaches/output.json index 9a367bd2c1..b89f4d6c83 100644 --- a/test/parser/samples/space-between-mustaches/output.json +++ b/test/parser/samples/space-between-mustaches/output.json @@ -26,6 +26,16 @@ "type": "Identifier", "start": 5, "end": 6, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 6 + } + }, "name": "a" } }, @@ -44,6 +54,16 @@ "type": "Identifier", "start": 9, "end": 10, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 10 + } + }, "name": "b" } }, @@ -62,6 +82,16 @@ "type": "Identifier", "start": 15, "end": 16, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 16 + } + }, "name": "c" } }, diff --git a/test/parser/samples/spread/output.json b/test/parser/samples/spread/output.json index 73a0dc9777..3a5c471905 100644 --- a/test/parser/samples/spread/output.json +++ b/test/parser/samples/spread/output.json @@ -18,6 +18,16 @@ "type": "Identifier", "start": 9, "end": 14, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 14 + } + }, "name": "props" } } diff --git a/test/parser/samples/textarea-children/output.json b/test/parser/samples/textarea-children/output.json index 90f31f3caf..919fa33fde 100644 --- a/test/parser/samples/textarea-children/output.json +++ b/test/parser/samples/textarea-children/output.json @@ -29,6 +29,16 @@ "type": "Identifier", "start": 41, "end": 44, + "loc": { + "start": { + "line": 2, + "column": 30 + }, + "end": { + "line": 2, + "column": 33 + } + }, "name": "foo" } }, diff --git a/test/parser/samples/whitespace-normal/output.json b/test/parser/samples/whitespace-normal/output.json index acbae7ae17..61eca1329f 100644 --- a/test/parser/samples/whitespace-normal/output.json +++ b/test/parser/samples/whitespace-normal/output.json @@ -33,6 +33,16 @@ "type": "Identifier", "start": 19, "end": 23, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 23 + } + }, "name": "name" } }, diff --git a/test/parser/samples/yield/input.svelte b/test/parser/samples/yield/input.svelte deleted file mode 100644 index f5e8aad053..0000000000 --- a/test/parser/samples/yield/input.svelte +++ /dev/null @@ -1 +0,0 @@ -{yield} \ No newline at end of file diff --git a/test/parser/samples/yield/output.json b/test/parser/samples/yield/output.json deleted file mode 100644 index b2e4b9430f..0000000000 --- a/test/parser/samples/yield/output.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "html": { - "start": 0, - "end": 7, - "type": "Fragment", - "children": [ - { - "start": 0, - "end": 7, - "type": "MustacheTag", - "expression": { - "type": "Identifier", - "start": 1, - "end": 6, - "name": "yield" - } - } - ] - } -} \ No newline at end of file diff --git a/test/runtime/samples/each-block-destructured-object-reserved-key/_config.js b/test/runtime/samples/each-block-destructured-object-reserved-key/_config.js new file mode 100644 index 0000000000..c04e984691 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-reserved-key/_config.js @@ -0,0 +1,5 @@ +export default { + html: ` +bar
+ ` +}; diff --git a/test/runtime/samples/each-block-destructured-object-reserved-key/main.svelte b/test/runtime/samples/each-block-destructured-object-reserved-key/main.svelte new file mode 100644 index 0000000000..c3e11c3ea1 --- /dev/null +++ b/test/runtime/samples/each-block-destructured-object-reserved-key/main.svelte @@ -0,0 +1,7 @@ + + +{#each foo as { in: bar }} +{bar}
+{/each} diff --git a/test/validator/samples/each-block-invalid-context-destructured-object/errors.json b/test/validator/samples/each-block-invalid-context-destructured-object/errors.json new file mode 100644 index 0000000000..085021ff5a --- /dev/null +++ b/test/validator/samples/each-block-invalid-context-destructured-object/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "unexpected-reserved-word", + "message": "'case' is a reserved word in JavaScript and cannot be used here", + "start": { + "line": 1, + "column": 18, + "character": 18 + }, + "end": { + "line": 1, + "column": 18, + "character": 18 + }, + "pos": 18 +}] diff --git a/test/validator/samples/each-block-invalid-context-destructured-object/input.svelte b/test/validator/samples/each-block-invalid-context-destructured-object/input.svelte new file mode 100644 index 0000000000..a891f131a0 --- /dev/null +++ b/test/validator/samples/each-block-invalid-context-destructured-object/input.svelte @@ -0,0 +1,3 @@ +{#each cases as { case }} + {case.title} +{/each}