From 286c60da4db2a068731e854b998061febb3bc98f Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 29 Mar 2021 22:55:52 -0400 Subject: [PATCH] fix missing slotted elements in AST (#6148) --- CHANGELOG.md | 1 + src/compiler/compile/Component.ts | 7 +- src/compiler/compile/nodes/Binding.ts | 3 +- src/compiler/compile/nodes/shared/Context.ts | 3 +- .../compile/nodes/shared/Expression.ts | 3 +- src/compiler/utils/clone.ts | 33 ++++++++ .../samples/slotted-element/input.svelte | 1 + .../samples/slotted-element/output.json | 42 ++++++++++ .../samples/textarea-children/output.json | 76 +++++++++---------- 9 files changed, 122 insertions(+), 47 deletions(-) create mode 100644 src/compiler/utils/clone.ts create mode 100644 test/parser/samples/slotted-element/input.svelte create mode 100644 test/parser/samples/slotted-element/output.json diff --git a/CHANGELOG.md b/CHANGELOG.md index a13b1377ec..c72df2436b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Export interfaces for transition parameters ([#5207](https://github.com/sveltejs/svelte/issues/5207)) * Export store's useful TypeScript definitions ([#5864](https://github.com/sveltejs/svelte/issues/5864)) * Fix previous breaking change to `svelte/preprocess` types location ([#6100](https://github.com/sveltejs/svelte/pull/6100)) +* Fix missing slotted elements in AST ([#6066](https://github.com/sveltejs/svelte/issues/6066)) ## 3.35.0 diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 70c25460b5..155b64778b 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -32,6 +32,7 @@ import { is_reserved_keyword } from './utils/reserved_keywords'; import { apply_preprocessor_sourcemap } from '../utils/mapped_code'; import Element from './nodes/Element'; import { DecodedSourceMap, RawSourceMap } from '@ampproject/remapping/dist/types/types'; +import { clone } from '../utils/clone'; interface ComponentOptions { namespace?: string; @@ -116,12 +117,12 @@ export default class Component { // the instance JS gets mutated, so we park // a copy here for later. TODO this feels gross - this.original_ast = { + this.original_ast = clone({ html: ast.html, css: ast.css, - instance: ast.instance && JSON.parse(JSON.stringify(ast.instance)), + instance: ast.instance, module: ast.module - }; + }); this.file = compile_options.filename && diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 3a9aa943e9..8fcc70ded9 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -9,6 +9,7 @@ import { TemplateNode } from '../../interfaces'; import Element from './Element'; import InlineComponent from './InlineComponent'; import Window from './Window'; +import { clone } from '../../utils/clone'; // TODO this should live in a specific binding const read_only_media_attributes = new Set([ @@ -42,7 +43,7 @@ export default class Binding extends Node { this.name = info.name; this.expression = new Expression(component, this, scope, info.expression); - this.raw_expression = JSON.parse(JSON.stringify(info.expression)); + this.raw_expression = clone(info.expression); const { name } = get_object(this.expression.node); diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts index c6ad2c2893..dbcdada587 100644 --- a/src/compiler/compile/nodes/shared/Context.ts +++ b/src/compiler/compile/nodes/shared/Context.ts @@ -2,6 +2,7 @@ import { x } from 'code-red'; import { Node, Identifier, Expression } from 'estree'; import { walk } from 'estree-walker'; import is_reference from 'is-reference'; +import { clone } from '../../../utils/clone'; export interface Context { key: Identifier; @@ -81,7 +82,7 @@ function update_reference(contexts: Context[], n: number, expression: Expression } // NOTE: avoid unnecessary deep clone? - expression = JSON.parse(JSON.stringify(expression)) as Expression; + expression = clone(expression) as Expression; walk(expression, { enter(node, parent: Node) { if (is_reference(node, parent)) { diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index caece14f00..cca81b6372 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -16,6 +16,7 @@ import { is_reserved_keyword } from '../../utils/reserved_keywords'; import replace_object from '../../utils/replace_object'; import is_contextual from './is_contextual'; import EachBlock from '../EachBlock'; +import { clone } from '../../../utils/clone'; type Owner = INode; @@ -195,7 +196,7 @@ export default class Expression { const node = walk(this.node, { enter(node: any, parent: any) { if (node.type === 'Property' && node.shorthand) { - node.value = JSON.parse(JSON.stringify(node.value)); + node.value = clone(node.value); node.shorthand = false; } diff --git a/src/compiler/utils/clone.ts b/src/compiler/utils/clone.ts new file mode 100644 index 0000000000..74b6cfa0a1 --- /dev/null +++ b/src/compiler/utils/clone.ts @@ -0,0 +1,33 @@ +// adapted from klona v2.0.4 - https://github.com/lukeed/klona +// (c) Luke Edwards, under MIT License + +// The sole modification is to skip function values in objects when cloning, so we don't break tests. + +export function clone(val) { + let k, out, tmp; + + if (Array.isArray(val)) { + out = Array(k=val.length); + while (k--) out[k] = (tmp=val[k]) && typeof tmp === 'object' ? clone(tmp) : tmp; + return out; + } + + if (Object.prototype.toString.call(val) === '[object Object]') { + out = {}; // null + for (k in val) { + if (k === '__proto__') { + Object.defineProperty(out, k, { + value: clone(val[k]), + configurable: true, + enumerable: true, + writable: true + }); + } else if (typeof val[k] !== 'function') { // MODIFICATION: skip functions + out[k] = (tmp=val[k]) && typeof tmp === 'object' ? clone(tmp) : tmp; + } + } + return out; + } + + return val; +} diff --git a/test/parser/samples/slotted-element/input.svelte b/test/parser/samples/slotted-element/input.svelte new file mode 100644 index 0000000000..709dede9a5 --- /dev/null +++ b/test/parser/samples/slotted-element/input.svelte @@ -0,0 +1 @@ +
diff --git a/test/parser/samples/slotted-element/output.json b/test/parser/samples/slotted-element/output.json new file mode 100644 index 0000000000..7028a367ff --- /dev/null +++ b/test/parser/samples/slotted-element/output.json @@ -0,0 +1,42 @@ +{ + "html": { + "start": 0, + "end": 45, + "type": "Fragment", + "children": [ + { + "start": 0, + "end": 45, + "type": "InlineComponent", + "name": "Component", + "attributes": [], + "children": [ + { + "start": 11, + "end": 33, + "type": "Element", + "name": "div", + "attributes": [ + { + "start": 16, + "end": 26, + "type": "Attribute", + "name": "slot", + "value": [ + { + "start": 22, + "end": 25, + "type": "Text", + "raw": "foo", + "data": "foo" + } + ] + } + ], + "children": [] + } + ] + } + ] + } +} diff --git a/test/parser/samples/textarea-children/output.json b/test/parser/samples/textarea-children/output.json index 919fa33fde..0ebbfd38fc 100644 --- a/test/parser/samples/textarea-children/output.json +++ b/test/parser/samples/textarea-children/output.json @@ -9,51 +9,45 @@ "end": 61, "type": "Element", "name": "textarea", - "attributes": [ + "attributes": [], + "children": [ { - "type": "Attribute", - "name": "value", - "value": [ - { - "start": 10, - "end": 41, - "type": "Text", - "raw": "\n\t

not actually an element. ", - "data": "\n\t

not actually an element. " - }, - { - "start": 40, - "end": 45, - "type": "MustacheTag", - "expression": { - "type": "Identifier", - "start": 41, - "end": 44, - "loc": { - "start": { - "line": 2, - "column": 30 - }, - "end": { - "line": 2, - "column": 33 - } - }, - "name": "foo" + "start": 10, + "end": 41, + "type": "Text", + "raw": "\n\t

not actually an element. ", + "data": "\n\t

not actually an element. " + }, + { + "start": 40, + "end": 45, + "type": "MustacheTag", + "expression": { + "type": "Identifier", + "start": 41, + "end": 44, + "loc": { + "start": { + "line": 2, + "column": 30 + }, + "end": { + "line": 2, + "column": 33 } }, - { - "start": 45, - "end": 50, - "type": "Text", - "raw": "

\n", - "data": "

\n" - } - ] + "name": "foo" + } + }, + { + "start": 45, + "end": 50, + "type": "Text", + "raw": "

\n", + "data": "

\n" } - ], - "children": [] + ] } ] } -} \ No newline at end of file +}