From a852760308e178f39e68b38f051a8c12584b62a7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 8 Dec 2018 23:25:46 -0500 Subject: [PATCH] fix instrumentation --- src/compile/Component.ts | 26 ++++++---- src/compile/nodes/shared/Expression.ts | 6 +-- src/compile/render-dom/index.ts | 51 ++++++++++++++++--- .../{remove_indentation.ts => indentation.ts} | 28 ++++++++++ .../expected.js | 4 +- .../samples/assignment-in-init/_config.js | 6 +++ .../samples/assignment-in-init/main.html | 7 +++ .../samples/computed-function/_config.js | 2 + 8 files changed, 109 insertions(+), 21 deletions(-) rename src/utils/{remove_indentation.ts => indentation.ts} (51%) create mode 100644 test/runtime/samples/assignment-in-init/_config.js create mode 100644 test/runtime/samples/assignment-in-init/main.html diff --git a/src/compile/Component.ts b/src/compile/Component.ts index b9ced34baf..a7054129c3 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -19,8 +19,9 @@ import addToSet from '../utils/addToSet'; import isReference from 'is-reference'; import TemplateScope from './nodes/shared/TemplateScope'; import fuzzymatch from '../utils/fuzzymatch'; -import { remove_indentation } from '../utils/remove_indentation'; +import { remove_indentation, add_indentation } from '../utils/indentation'; import getObject from '../utils/getObject'; +import deindent from '../utils/deindent'; type Meta = { namespace?: string; @@ -614,34 +615,36 @@ export default class Component { coalesced_declarations.forEach(group => { const kind = group[0].kind; - let replacement = ''; + let c = 0; let combining = false; group.forEach(node => { - node.declarations.forEach(({ id, init }) => { + node.declarations.forEach(declarator => { + const { id, init } = declarator; + if (id.type === 'Identifier') { const value = init ? this.code.slice(id.start, init.end) : this.code.slice(id.start, id.end); if (combining) { - replacement += `, ${value}`; + code.overwrite(c, id.start, ', '); } else { - replacement += `${kind} { ${value}`; + code.appendLeft(id.start, '{ '); combining = true; } } else { throw new Error('TODO destructured declarations'); } + + c = declarator.end; }); }); if (combining) { - replacement += ' } = $$props;'; + code.prependRight(c, ' } = $$props'); } - - this.code.overwrite(group[0].start, group[group.length - 1].end, replacement); }); } @@ -796,13 +799,18 @@ export default class Component { } }); + add_indentation(this.code, node.body, 2); + unsorted_reactive_declarations.push({ assignees, dependencies, node, snippet: node.body.type === 'BlockStatement' ? `[✂${node.body.start}-${node.end}✂]` - : `{ [✂${node.body.start}-${node.end}✂] }` + : deindent` + { + [✂${node.body.start}-${node.end}✂] + }` }); } }); diff --git a/src/compile/nodes/shared/Expression.ts b/src/compile/nodes/shared/Expression.ts index 1a09d0ebe4..dbe5f9bd7f 100644 --- a/src/compile/nodes/shared/Expression.ts +++ b/src/compile/nodes/shared/Expression.ts @@ -4,12 +4,12 @@ import isReference from 'is-reference'; import flattenReference from '../../../utils/flattenReference'; import { createScopes, Scope } from '../../../utils/annotateWithScopes'; import { Node } from '../../../interfaces'; -import addToSet from '../../../utils/addToSet'; import globalWhitelist from '../../../utils/globalWhitelist'; import deindent from '../../../utils/deindent'; import Wrapper from '../../render-dom/wrappers/shared/Wrapper'; import sanitize from '../../../utils/sanitize'; import TemplateScope from './TemplateScope'; +import getObject from '../../../utils/getObject'; const binaryOperators: Record = { '**': 15, @@ -248,7 +248,7 @@ export default class Expression { if (function_expression) { if (node.type === 'AssignmentExpression') { // TODO handle destructuring assignments - const { name } = flattenReference(node.left); + const { name } = getObject(node.left); pending_assignments.add(name); } } else { @@ -296,7 +296,7 @@ export default class Expression { let body = code.slice(node.body.start, node.body.end).trim(); if (node.body.type !== 'BlockStatement') { if (pending_assignments.size > 0) { - const insert = [...pending_assignments].map(name => `$$make_dirty('${name}');`); + const insert = [...pending_assignments].map(name => `$$make_dirty('${name}')`).join('; '); pending_assignments = new Set(); component.has_reactive_assignments = true; diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index 472d51991e..61e1ebc6b1 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -13,7 +13,7 @@ export default function dom( component: Component, options: CompileOptions ) { - const { name } = component; + const { name, code } = component; const renderer = new Renderer(component, options); const { block } = renderer; @@ -153,27 +153,63 @@ export default function dom( let scope = component.instance_scope; let map = component.instance_scope_map; + let pending_assignments = new Set(); + walk(component.instance_script.content, { enter: (node, parent) => { if (map.has(node)) { scope = map.get(node); } + }, + + leave(node) { + if (map.has(node)) { + scope = scope.parent; + } if (node.type === 'AssignmentExpression') { const { name } = getObject(node.left); if (scope.findOwner(name) === component.instance_scope) { - component.instrument(node, parent, name, false); + pending_assignments.add(name); + component.has_reactive_assignments = true; } } - }, - leave(node) { - if (map.has(node)) { - scope = scope.parent; + if (pending_assignments.size > 0) { + if (node.type === 'ArrowFunctionExpression') { + const insert = [...pending_assignments].map(name => `$$make_dirty('${name}')`).join(';'); + pending_assignments = new Set(); + + code.prependRight(node.body.start, `{ const $$result = `); + code.appendLeft(node.body.end, `; ${insert}; return $$result; }`); + + pending_assignments = new Set(); + } + + else if (/Statement/.test(node.type)) { + const insert = [...pending_assignments].map(name => `$$make_dirty('${name}')`).join('; '); + + if (/^(Break|Continue|Return)Statement/.test(node.type)) { + if (node.argument) { + code.overwrite(node.start, node.argument.start, `var $$result = `); + code.appendLeft(node.argument.end, `; ${insert}; return $$result`); + } else { + code.prependRight(node.start, `${insert}; `); + } + } else { + code.appendLeft(node.end, `${code.original[node.end - 1] === ';' ? '' : ';'} ${insert};`); + } + + pending_assignments = new Set(); + } } } }); + + if (pending_assignments.size > 0) { + throw new Error(`TODO this should not happen!`); + } } const args = ['$$self']; @@ -237,8 +273,7 @@ export default function dom( ${component.reactive_declarations.length > 0 && deindent` $$self.$$.update = ($$dirty = { ${Array.from(all_reactive_dependencies).map(n => `${n}: 1`).join(', ')} }) => { ${component.reactive_declarations.map(d => deindent` - if (${Array.from(d.dependencies).map(n => `$$dirty.${n}`).join(' || ')}) ${d.snippet} - `)} + if (${Array.from(d.dependencies).map(n => `$$dirty.${n}`).join(' || ')}) ${d.snippet}`)} }; `} diff --git a/src/utils/remove_indentation.ts b/src/utils/indentation.ts similarity index 51% rename from src/utils/remove_indentation.ts rename to src/utils/indentation.ts index 2d66853f68..765a62bfbf 100644 --- a/src/utils/remove_indentation.ts +++ b/src/utils/indentation.ts @@ -1,6 +1,7 @@ import MagicString from 'magic-string'; import { Node } from '../interfaces'; import { walk } from 'estree-walker'; +import repeat from './repeat'; export function remove_indentation(code: MagicString, node: Node) { const indent = code.getIndentString(); @@ -26,4 +27,31 @@ export function remove_indentation(code: MagicString, node: Node) { code.remove(index, index + indent.length); } +} + +export function add_indentation(code: MagicString, node: Node, levels = 1) { + const base_indent = code.getIndentString(); + const indent = repeat(base_indent, levels); + const pattern = /\n/gm; + + const excluded = []; + + walk(node, { + enter(node) { + if (node.type === 'TemplateElement') { + excluded.push(node); + } + } + }); + + const str = code.original.slice(node.start, node.end); + + let match; + while (match = pattern.exec(str)) { + const index = node.start + match.index; + while (excluded[0] && excluded[0].end < index) excluded.shift(); + if (excluded[0] && excluded[0].start < index) continue; + + code.appendLeft(index + 1, indent); + } } \ No newline at end of file diff --git a/test/js/samples/dev-warning-missing-data-computed/expected.js b/test/js/samples/dev-warning-missing-data-computed/expected.js index d7c22a1711..27b68d1e95 100644 --- a/test/js/samples/dev-warning-missing-data-computed/expected.js +++ b/test/js/samples/dev-warning-missing-data-computed/expected.js @@ -64,7 +64,9 @@ function define($$self, $$props, $$make_dirty) { }; $$self.$$.update = ($$dirty = { foo: 1 }) => { - if ($$dirty.foo) { bar = foo * 2; $$make_dirty('bar'); } + if ($$dirty.foo) { + bar = foo * 2; $$make_dirty('bar'); + } }; } diff --git a/test/runtime/samples/assignment-in-init/_config.js b/test/runtime/samples/assignment-in-init/_config.js new file mode 100644 index 0000000000..bcf227f8e6 --- /dev/null +++ b/test/runtime/samples/assignment-in-init/_config.js @@ -0,0 +1,6 @@ +export default { + test({ assert, component }) { + assert.equal(component.get_foo(), 1); + assert.equal(component.get_bar(), 2); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/assignment-in-init/main.html b/test/runtime/samples/assignment-in-init/main.html new file mode 100644 index 0000000000..235b90b6d5 --- /dev/null +++ b/test/runtime/samples/assignment-in-init/main.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/test/runtime/samples/computed-function/_config.js b/test/runtime/samples/computed-function/_config.js index b3c6a65dbb..a29c022b5f 100644 --- a/test/runtime/samples/computed-function/_config.js +++ b/test/runtime/samples/computed-function/_config.js @@ -2,8 +2,10 @@ export default { html: '

50

', test({ assert, component, target }) { + console.group('range [50,100]'); component.range = [50, 100]; assert.htmlEqual(target.innerHTML, '

75

'); + console.groupEnd(); component.range = [50, 60]; assert.htmlEqual(target.innerHTML, '

55

');