From cbbfa954de188e12b9fc2657e8f983a78098b579 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Nov 2018 14:09:43 -0500 Subject: [PATCH 01/52] set up for v3 --- package.json | 3 +- src/cli.js | 122 ++++---- src/index.js | 269 +----------------- src/v2/index.js | 267 +++++++++++++++++ src/v3/index.js | 228 +++++++++++++++ test/test.js | 23 +- .../await-pending-then-catch/input.html | 0 .../await-pending-then-catch/output.html | 0 .../samples/await-then-catch/input.html | 0 .../samples/await-then-catch/output.html | 0 test/{ => v2}/samples/await-then/input.html | 0 test/{ => v2}/samples/await-then/output.html | 0 .../builtins-component-complex/input.html | 0 .../builtins-component-complex/output.html | 0 .../samples/builtins-component/input.html | 0 .../samples/builtins-component/output.html | 0 .../{ => v2}/samples/builtins-head/input.html | 0 .../samples/builtins-head/output.html | 0 .../{ => v2}/samples/builtins-self/input.html | 0 .../samples/builtins-self/output.html | 0 .../samples/builtins-window/input.html | 0 .../samples/builtins-window/output.html | 0 .../samples/computed-after-style/input.html | 0 .../samples/computed-after-style/output.html | 0 test/{ => v2}/samples/computed/input.html | 0 test/{ => v2}/samples/computed/output.html | 0 .../samples/each-block-keyed/input.html | 0 .../samples/each-block-keyed/output.html | 0 test/{ => v2}/samples/each-block/input.html | 0 test/{ => v2}/samples/each-block/output.html | 0 test/{ => v2}/samples/each-else/input.html | 0 test/{ => v2}/samples/each-else/output.html | 0 .../{ => v2}/samples/escape-braces/input.html | 0 .../samples/escape-braces/output.html | 0 .../event-handler-shorthand/input.html | 0 .../event-handler-shorthand/output.html | 0 .../{ => v2}/samples/if-block-else/input.html | 0 .../samples/if-block-else/output.html | 0 .../samples/if-block-elseif/input.html | 0 .../samples/if-block-elseif/output.html | 0 test/{ => v2}/samples/if-block/input.html | 0 test/{ => v2}/samples/if-block/output.html | 0 test/{ => v2}/samples/if-else-if/input.html | 0 test/{ => v2}/samples/if-else-if/output.html | 0 .../samples/ignore-bad-css/input.html | 0 .../samples/ignore-bad-css/output.html | 0 test/{ => v2}/samples/raw-tag/input.html | 0 test/{ => v2}/samples/raw-tag/output.html | 0 .../samples/shorthand-properties/input.html | 0 .../samples/shorthand-properties/output.html | 0 test/{ => v2}/samples/spread/input.html | 0 test/{ => v2}/samples/spread/output.html | 0 test/{ => v2}/samples/store-method/input.html | 0 .../{ => v2}/samples/store-method/output.html | 0 test/{ => v2}/samples/tag/input.html | 0 test/{ => v2}/samples/tag/output.html | 0 test/{ => v2}/samples/yield/input.html | 0 test/{ => v2}/samples/yield/output.html | 0 test/v3/samples/script-less/input.html | 1 + test/v3/samples/script-less/output.html | 1 + yarn.lock | 7 +- 61 files changed, 585 insertions(+), 336 deletions(-) create mode 100644 src/v2/index.js create mode 100644 src/v3/index.js rename test/{ => v2}/samples/await-pending-then-catch/input.html (100%) rename test/{ => v2}/samples/await-pending-then-catch/output.html (100%) rename test/{ => v2}/samples/await-then-catch/input.html (100%) rename test/{ => v2}/samples/await-then-catch/output.html (100%) rename test/{ => v2}/samples/await-then/input.html (100%) rename test/{ => v2}/samples/await-then/output.html (100%) rename test/{ => v2}/samples/builtins-component-complex/input.html (100%) rename test/{ => v2}/samples/builtins-component-complex/output.html (100%) rename test/{ => v2}/samples/builtins-component/input.html (100%) rename test/{ => v2}/samples/builtins-component/output.html (100%) rename test/{ => v2}/samples/builtins-head/input.html (100%) rename test/{ => v2}/samples/builtins-head/output.html (100%) rename test/{ => v2}/samples/builtins-self/input.html (100%) rename test/{ => v2}/samples/builtins-self/output.html (100%) rename test/{ => v2}/samples/builtins-window/input.html (100%) rename test/{ => v2}/samples/builtins-window/output.html (100%) rename test/{ => v2}/samples/computed-after-style/input.html (100%) rename test/{ => v2}/samples/computed-after-style/output.html (100%) rename test/{ => v2}/samples/computed/input.html (100%) rename test/{ => v2}/samples/computed/output.html (100%) rename test/{ => v2}/samples/each-block-keyed/input.html (100%) rename test/{ => v2}/samples/each-block-keyed/output.html (100%) rename test/{ => v2}/samples/each-block/input.html (100%) rename test/{ => v2}/samples/each-block/output.html (100%) rename test/{ => v2}/samples/each-else/input.html (100%) rename test/{ => v2}/samples/each-else/output.html (100%) rename test/{ => v2}/samples/escape-braces/input.html (100%) rename test/{ => v2}/samples/escape-braces/output.html (100%) rename test/{ => v2}/samples/event-handler-shorthand/input.html (100%) rename test/{ => v2}/samples/event-handler-shorthand/output.html (100%) rename test/{ => v2}/samples/if-block-else/input.html (100%) rename test/{ => v2}/samples/if-block-else/output.html (100%) rename test/{ => v2}/samples/if-block-elseif/input.html (100%) rename test/{ => v2}/samples/if-block-elseif/output.html (100%) rename test/{ => v2}/samples/if-block/input.html (100%) rename test/{ => v2}/samples/if-block/output.html (100%) rename test/{ => v2}/samples/if-else-if/input.html (100%) rename test/{ => v2}/samples/if-else-if/output.html (100%) rename test/{ => v2}/samples/ignore-bad-css/input.html (100%) rename test/{ => v2}/samples/ignore-bad-css/output.html (100%) rename test/{ => v2}/samples/raw-tag/input.html (100%) rename test/{ => v2}/samples/raw-tag/output.html (100%) rename test/{ => v2}/samples/shorthand-properties/input.html (100%) rename test/{ => v2}/samples/shorthand-properties/output.html (100%) rename test/{ => v2}/samples/spread/input.html (100%) rename test/{ => v2}/samples/spread/output.html (100%) rename test/{ => v2}/samples/store-method/input.html (100%) rename test/{ => v2}/samples/store-method/output.html (100%) rename test/{ => v2}/samples/tag/input.html (100%) rename test/{ => v2}/samples/tag/output.html (100%) rename test/{ => v2}/samples/yield/input.html (100%) rename test/{ => v2}/samples/yield/output.html (100%) create mode 100644 test/v3/samples/script-less/input.html create mode 100644 test/v3/samples/script-less/output.html diff --git a/package.json b/package.json index c245d31..5628eb8 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "magic-string": "^0.25.1", "prompts": "^1.2.0", "sade": "^1.4.1", - "svelte": "1", + "svelte-1": "npm:svelte@1", + "svelte-2": "npm:svelte@2", "turbocolor": "^2.6.1" } } diff --git a/src/cli.js b/src/cli.js index be2fe7c..eb8bfb6 100644 --- a/src/cli.js +++ b/src/cli.js @@ -19,72 +19,78 @@ function mkdirp(dir) { } } -prog.command(`v2 `) - .describe(`upgrade file/directory to v2 syntax`) - .option(`-o, --output`, `Write new files, instead of overwriting input files`) - .option(`-f, --force`, `Don't ask before overwriting files`) - .example(`v2 MyComponent.html`) - .example(`v2 MyComponent.html -o My-Component.v2.html`) - .example(`v2 src`) - .action(async (input, opts) => { - try { - const stats = fs.statSync(input); - - const upgrade = await import('./index.js'); - - let output = opts.output || input; - - if (stats.isDirectory()) { - const files = glob.sync('**/*.+(html|svelte)', { cwd: input }); - input = files.map(file => path.join(input, file)); - output = files.map(file => path.join(output, file)); - } else { - input = [input]; - output = [output]; - } +[2, 3].forEach(v => { + prog.command(`v${v} `) + .describe(`upgrade file/directory to v${v} syntax`) + .option(`-o, --output`, `Write new files, instead of overwriting input files`) + .option(`-f, --force`, `Don't ask before overwriting files`) + .example(`v${v} MyComponent.html`) + .example(`v${v} MyComponent.html -o My-Component.v${v}.html`) + .example(`v${v} src`) + .action(async (input, opts) => { + try { + const stats = fs.statSync(input); + + const upgrade = v === 2 + ? await import('./v2/index.js') + : await import('./v3/index.js'); + + let output = opts.output || input; + + if (stats.isDirectory()) { + const files = glob.sync('**/*.+(html|htmlx|svelte)', { cwd: input }); + input = files.map(file => path.join(input, file)); + output = files.map(file => path.join(output, file)); + } else { + input = [input]; + output = [output]; + } - if (!opts.force) { - const existing = output.filter(file => fs.existsSync(file)); - if (existing.length > 0) { - console.error(tc.cyan(`This will overwrite the following files:`)); - console.error(tc.gray(existing.join('\n'))) - - const response = await prompts({ - type: 'confirm', - name: 'value', - message: `Overwrite ${existing.length} ${existing.length === 1 ? 'file' : 'files'}?`, - initial: true - }); - - if (response.value === false) { - console.error(tc.cyan('Aborted')); - return; + if (!opts.force) { + const existing = output.filter(file => fs.existsSync(file)); + if (existing.length > 0) { + console.error(tc.cyan(`This will overwrite the following files:`)); + console.error(tc.gray(existing.join('\n'))) + + const response = await prompts({ + type: 'confirm', + name: 'value', + message: `Overwrite ${existing.length} ${existing.length === 1 ? 'file' : 'files'}?`, + initial: true + }); + + if (response.value === false) { + console.error(tc.cyan('Aborted')); + return; + } } } - } - input.forEach((src, i) => { - const dest = output[i]; + input.forEach((src, i) => { + const dest = output[i]; - try { - const upgraded = upgrade.upgradeTemplate(fs.readFileSync(src, 'utf-8')); + try { + const upgraded = upgrade.upgradeTemplate(fs.readFileSync(src, 'utf-8')); - mkdirp(path.dirname(dest)); - fs.writeFileSync(dest, upgraded); - } catch (err) { - console.error(tc.bold.red(`Error transforming ${src}:`)); - console.error(tc.red(err.message)); + mkdirp(path.dirname(dest)); + fs.writeFileSync(dest, upgraded); + } catch (err) { + console.error(tc.bold.red(`Error transforming ${src}:`)); + console.error(tc.red(err.message)); - if (err.frame) { - console.error(err.frame); + if (err.frame) { + console.error(err.frame); + } } - } - }); + }); + + console.error(tc.cyan(`Wrote ${output.length} ${output.length === 1 ? 'file' : 'files'}`)) + } catch (err) { + console.error(tc.red(err.message)); + } + }); +}); + - console.error(tc.cyan(`Wrote ${output.length} ${output.length === 1 ? 'file' : 'files'}`)) - } catch (err) { - console.error(tc.red(err.message)); - } - }); prog.parse(process.argv); diff --git a/src/index.js b/src/index.js index 379ca91..1e1a072 100644 --- a/src/index.js +++ b/src/index.js @@ -1,267 +1,2 @@ -import * as svelte from 'svelte'; -import MagicString from 'magic-string'; -import { walk, childKeys } from 'estree-walker'; - -// We need to tell estree-walker that it should always -// look for an `else` block, otherwise it might get -// the wrong idea about the shape of each/if blocks -childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; -childKeys.Attribute = ['value']; - -function hasElseIf(source, node) { - const last = node.children[node.children.length - 1]; - - let c = last.end; - while (source[c] !== '{') c += 1; - while (source[c] === '{') c += 1; - while (/\s/.test(source[c])) c += 1; - - return source.slice(c, c + 6) === 'elseif'; -} - -function flattenReference(node) { - const parts = []; - const propEnd = node.end; - - while (node.type === 'MemberExpression') { - if (node.computed) return null; - parts.unshift(node.property); - - node = node.object; - } - - parts.unshift(node); - - const propStart = node.end; - const name = node.type === 'Identifier' - ? node.name - : node.type === 'ThisExpression' ? 'this' : null; - - if (!name) return null; - - return { name, parts }; -} - - -export function upgradeTemplate(source) { - const code = new MagicString(source); - const ast = svelte.parse(source.replace(//gm, m => { - let spaces = ''; - let i = m.length - 15; - while (i-- > 0) spaces += ' '; - - return ``; - })); - - function trimStart(node) { - let c = node.start; - - code.remove(c, c + 1); - - c = node.expression.end; - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - function trimEnd(node) { - let c = node.end; - - code.remove(c - 1, c); - - while (source[c - 1] !== '{') c -= 1; - code.remove(c - 1, c); - } - - function trim(node) { - trimStart(node); - trimEnd(node); - } - - const properties = {}; - const methods = {}; - - if (ast.js) { - const defaultExport = ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); - if (defaultExport) { - defaultExport.declaration.properties.forEach(prop => { - properties[prop.key.name] = prop.value; - }); - - if (properties.computed) { - properties.computed.properties.forEach(prop => { - const { params } = prop.value; - - if (prop.value.type === 'FunctionExpression') { - let a = prop.value.start; - if (!prop.method) a += 8; - while (source[a] !== '(') a += 1; - - let b = params[0].start; - code.overwrite(a, b, '({ '); - - a = b = params[params.length - 1].end; - while (source[b] !== ')') b += 1; - code.overwrite(a, b + 1, ' })'); - } else if (prop.value.type === 'ArrowFunctionExpression') { - let a = prop.value.start; - let b = params[0].start; - - if (a !== b) code.remove(a, b); - code.prependRight(b, '({ '); - - a = b = params[params.length - 1].end; - while (source[b] !== '=') b += 1; - - if (a !== b) code.remove(a, b); - code.appendLeft(a, ' }) '); - } - }); - } - - if (properties.methods) { - properties.methods.properties.forEach(prop => { - methods[prop.key.name] = prop.value; - }); - } - } - } - - walk(ast.html, { - enter(node) { - let a = node.start; - let b = node.end; - - switch (node.type) { - case 'MustacheTag': - trimStart(node); - break; - - case 'RawMustacheTag': - code.overwrite(a + 1, node.expression.start, '@html ').remove(b - 2, b); - break; - - case 'AwaitBlock': - trim(node); - - if (node.pending.start !== null) { - let c = node.then.start; - code.overwrite(c + 1, c + 2, ':'); - - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - if (node.catch.start !== null) { - let c = node.catch.start; - code.overwrite(c + 1, c + 2, ':'); - - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - break; - - case 'IfBlock': - case 'EachBlock': - if (!node.skip) trim(node); - - if (node.else) { - let c = node.children[node.children.length - 1].end; - while (source[c] !== '{') c += 1; - code.overwrite(c + 1, c + 2, ':'); - - if (hasElseIf(source, node)) { - c = node.else.children[0].expression.end; - node.else.children[0].skip = true; - } - - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - if (node.key) { - let a = node.expression.end; - while (source[a] !== '@') a += 1; - code.overwrite(a, a + 1, `(${node.context}.`); - - while (!/\w/.test(source[a])) a += 1; - while (/\w/.test(source[a])) a += 1; - code.appendLeft(a, ')'); - } - - break; - - case 'Element': - case 'Window': - case 'Head': - if (node.name === 'slot' && /{{\s*yield\s*}}/.test(source.slice(a, b))) { - code.overwrite(a, b, ''); - } - - else if (node.name[0] === ':') { - const name = `svelte:${node.name[1].toLowerCase()}`; - code.overwrite(a + 1, a + 3, name); - - while (source[b - 1] !== '<') b -= 1; - if (source[b] === '/') { - code.overwrite(b + 1, b + 3, name); - } - } - - if (node.name === ':Component') { - a = node.expression.start; - while (source[a - 1] !== '{') a -= 1; - - b = node.expression.end; - while (source[b] !== '}') b += 1; - - const shouldQuote = /\s/.test(source.slice(a, b)); - if (shouldQuote) code.prependRight(a - 1, '"').appendLeft(b + 1, '"'); - code.prependRight(a - 1, 'this=') - } - - break; - - case 'Text': - let c = -1; - while ((c = node.data.indexOf('{', c + 1)) !== -1) { - code.overwrite(a + c, a + c + 1, '{'); - } - break; - - case 'Attribute': - if (source[a] === ':') { - code.overwrite(a, a + 1, '{').appendLeft(b, '}'); - } - - break; - - case 'Spread': - code.remove(a, a + 1).remove(b - 1, b); - break; - - case 'EventHandler': - if (node.expression) { - const { name, parts } = flattenReference(node.expression.callee); - if (name === 'store') { - if (`$${parts[1].name}` in methods) { - console.error(`Not overwriting store method — $${parts[1].name} already exists on component`); - } else { - code.overwrite(node.expression.start, parts[1].start, '$'); - } - } - } - - break; - } - }, - - leave(node) { - - } - }); - - - - return code.toString(); -} \ No newline at end of file +export { upgradeTemplate as v2 } from './v2/index.js'; +export { upgradeTemplate as v3 } from './v3/index.js'; \ No newline at end of file diff --git a/src/v2/index.js b/src/v2/index.js new file mode 100644 index 0000000..95c1c7f --- /dev/null +++ b/src/v2/index.js @@ -0,0 +1,267 @@ +import * as svelte from 'svelte-1'; +import MagicString from 'magic-string'; +import { walk, childKeys } from 'estree-walker'; + +// We need to tell estree-walker that it should always +// look for an `else` block, otherwise it might get +// the wrong idea about the shape of each/if blocks +childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; +childKeys.Attribute = ['value']; + +function hasElseIf(source, node) { + const last = node.children[node.children.length - 1]; + + let c = last.end; + while (source[c] !== '{') c += 1; + while (source[c] === '{') c += 1; + while (/\s/.test(source[c])) c += 1; + + return source.slice(c, c + 6) === 'elseif'; +} + +function flattenReference(node) { + const parts = []; + const propEnd = node.end; + + while (node.type === 'MemberExpression') { + if (node.computed) return null; + parts.unshift(node.property); + + node = node.object; + } + + parts.unshift(node); + + const propStart = node.end; + const name = node.type === 'Identifier' + ? node.name + : node.type === 'ThisExpression' ? 'this' : null; + + if (!name) return null; + + return { name, parts }; +} + + +export function upgradeTemplate(source) { + const code = new MagicString(source); + const ast = svelte.parse(source.replace(//gm, m => { + let spaces = ''; + let i = m.length - 15; + while (i-- > 0) spaces += ' '; + + return ``; + })); + + function trimStart(node) { + let c = node.start; + + code.remove(c, c + 1); + + c = node.expression.end; + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + function trimEnd(node) { + let c = node.end; + + code.remove(c - 1, c); + + while (source[c - 1] !== '{') c -= 1; + code.remove(c - 1, c); + } + + function trim(node) { + trimStart(node); + trimEnd(node); + } + + const properties = {}; + const methods = {}; + + if (ast.js) { + const defaultExport = ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); + if (defaultExport) { + defaultExport.declaration.properties.forEach(prop => { + properties[prop.key.name] = prop.value; + }); + + if (properties.computed) { + properties.computed.properties.forEach(prop => { + const { params } = prop.value; + + if (prop.value.type === 'FunctionExpression') { + let a = prop.value.start; + if (!prop.method) a += 8; + while (source[a] !== '(') a += 1; + + let b = params[0].start; + code.overwrite(a, b, '({ '); + + a = b = params[params.length - 1].end; + while (source[b] !== ')') b += 1; + code.overwrite(a, b + 1, ' })'); + } else if (prop.value.type === 'ArrowFunctionExpression') { + let a = prop.value.start; + let b = params[0].start; + + if (a !== b) code.remove(a, b); + code.prependRight(b, '({ '); + + a = b = params[params.length - 1].end; + while (source[b] !== '=') b += 1; + + if (a !== b) code.remove(a, b); + code.appendLeft(a, ' }) '); + } + }); + } + + if (properties.methods) { + properties.methods.properties.forEach(prop => { + methods[prop.key.name] = prop.value; + }); + } + } + } + + walk(ast.html, { + enter(node) { + let a = node.start; + let b = node.end; + + switch (node.type) { + case 'MustacheTag': + trimStart(node); + break; + + case 'RawMustacheTag': + code.overwrite(a + 1, node.expression.start, '@html ').remove(b - 2, b); + break; + + case 'AwaitBlock': + trim(node); + + if (node.pending.start !== null) { + let c = node.then.start; + code.overwrite(c + 1, c + 2, ':'); + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + if (node.catch.start !== null) { + let c = node.catch.start; + code.overwrite(c + 1, c + 2, ':'); + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + break; + + case 'IfBlock': + case 'EachBlock': + if (!node.skip) trim(node); + + if (node.else) { + let c = node.children[node.children.length - 1].end; + while (source[c] !== '{') c += 1; + code.overwrite(c + 1, c + 2, ':'); + + if (hasElseIf(source, node)) { + c = node.else.children[0].expression.end; + node.else.children[0].skip = true; + } + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + if (node.key) { + let a = node.expression.end; + while (source[a] !== '@') a += 1; + code.overwrite(a, a + 1, `(${node.context}.`); + + while (!/\w/.test(source[a])) a += 1; + while (/\w/.test(source[a])) a += 1; + code.appendLeft(a, ')'); + } + + break; + + case 'Element': + case 'Window': + case 'Head': + if (node.name === 'slot' && /{{\s*yield\s*}}/.test(source.slice(a, b))) { + code.overwrite(a, b, ''); + } + + else if (node.name[0] === ':') { + const name = `svelte:${node.name[1].toLowerCase()}`; + code.overwrite(a + 1, a + 3, name); + + while (source[b - 1] !== '<') b -= 1; + if (source[b] === '/') { + code.overwrite(b + 1, b + 3, name); + } + } + + if (node.name === ':Component') { + a = node.expression.start; + while (source[a - 1] !== '{') a -= 1; + + b = node.expression.end; + while (source[b] !== '}') b += 1; + + const shouldQuote = /\s/.test(source.slice(a, b)); + if (shouldQuote) code.prependRight(a - 1, '"').appendLeft(b + 1, '"'); + code.prependRight(a - 1, 'this=') + } + + break; + + case 'Text': + let c = -1; + while ((c = node.data.indexOf('{', c + 1)) !== -1) { + code.overwrite(a + c, a + c + 1, '{'); + } + break; + + case 'Attribute': + if (source[a] === ':') { + code.overwrite(a, a + 1, '{').appendLeft(b, '}'); + } + + break; + + case 'Spread': + code.remove(a, a + 1).remove(b - 1, b); + break; + + case 'EventHandler': + if (node.expression) { + const { name, parts } = flattenReference(node.expression.callee); + if (name === 'store') { + if (`$${parts[1].name}` in methods) { + console.error(`Not overwriting store method — $${parts[1].name} already exists on component`); + } else { + code.overwrite(node.expression.start, parts[1].start, '$'); + } + } + } + + break; + } + }, + + leave(node) { + + } + }); + + + + return code.toString(); +} \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js new file mode 100644 index 0000000..e5e85c9 --- /dev/null +++ b/src/v3/index.js @@ -0,0 +1,228 @@ +import * as svelte from 'svelte-2'; +import MagicString from 'magic-string'; +import { walk, childKeys } from 'estree-walker'; + +// We need to tell estree-walker that it should always +// look for an `else` block, otherwise it might get +// the wrong idea about the shape of each/if blocks +childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; +childKeys.Attribute = ['value']; + +export function upgradeTemplate(source) { + const code = new MagicString(source); + const { ast } = svelte.compile(source); + + throw new Error('TODO'); + + function trimStart(node) { + let c = node.start; + + code.remove(c, c + 1); + + c = node.expression.end; + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + function trimEnd(node) { + let c = node.end; + + code.remove(c - 1, c); + + while (source[c - 1] !== '{') c -= 1; + code.remove(c - 1, c); + } + + function trim(node) { + trimStart(node); + trimEnd(node); + } + + const properties = {}; + const methods = {}; + + if (ast.js) { + const defaultExport = ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); + if (defaultExport) { + defaultExport.declaration.properties.forEach(prop => { + properties[prop.key.name] = prop.value; + }); + + if (properties.computed) { + properties.computed.properties.forEach(prop => { + const { params } = prop.value; + + if (prop.value.type === 'FunctionExpression') { + let a = prop.value.start; + if (!prop.method) a += 8; + while (source[a] !== '(') a += 1; + + let b = params[0].start; + code.overwrite(a, b, '({ '); + + a = b = params[params.length - 1].end; + while (source[b] !== ')') b += 1; + code.overwrite(a, b + 1, ' })'); + } else if (prop.value.type === 'ArrowFunctionExpression') { + let a = prop.value.start; + let b = params[0].start; + + if (a !== b) code.remove(a, b); + code.prependRight(b, '({ '); + + a = b = params[params.length - 1].end; + while (source[b] !== '=') b += 1; + + if (a !== b) code.remove(a, b); + code.appendLeft(a, ' }) '); + } + }); + } + + if (properties.methods) { + properties.methods.properties.forEach(prop => { + methods[prop.key.name] = prop.value; + }); + } + } + } + + walk(ast.html, { + enter(node) { + let a = node.start; + let b = node.end; + + switch (node.type) { + case 'MustacheTag': + trimStart(node); + break; + + case 'RawMustacheTag': + code.overwrite(a + 1, node.expression.start, '@html ').remove(b - 2, b); + break; + + case 'AwaitBlock': + trim(node); + + if (node.pending.start !== null) { + let c = node.then.start; + code.overwrite(c + 1, c + 2, ':'); + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + if (node.catch.start !== null) { + let c = node.catch.start; + code.overwrite(c + 1, c + 2, ':'); + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + break; + + case 'IfBlock': + case 'EachBlock': + if (!node.skip) trim(node); + + if (node.else) { + let c = node.children[node.children.length - 1].end; + while (source[c] !== '{') c += 1; + code.overwrite(c + 1, c + 2, ':'); + + if (hasElseIf(source, node)) { + c = node.else.children[0].expression.end; + node.else.children[0].skip = true; + } + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + if (node.key) { + let a = node.expression.end; + while (source[a] !== '@') a += 1; + code.overwrite(a, a + 1, `(${node.context}.`); + + while (!/\w/.test(source[a])) a += 1; + while (/\w/.test(source[a])) a += 1; + code.appendLeft(a, ')'); + } + + break; + + case 'Element': + case 'Window': + case 'Head': + if (node.name === 'slot' && /{{\s*yield\s*}}/.test(source.slice(a, b))) { + code.overwrite(a, b, ''); + } + + else if (node.name[0] === ':') { + const name = `svelte:${node.name[1].toLowerCase()}`; + code.overwrite(a + 1, a + 3, name); + + while (source[b - 1] !== '<') b -= 1; + if (source[b] === '/') { + code.overwrite(b + 1, b + 3, name); + } + } + + if (node.name === ':Component') { + a = node.expression.start; + while (source[a - 1] !== '{') a -= 1; + + b = node.expression.end; + while (source[b] !== '}') b += 1; + + const shouldQuote = /\s/.test(source.slice(a, b)); + if (shouldQuote) code.prependRight(a - 1, '"').appendLeft(b + 1, '"'); + code.prependRight(a - 1, 'this=') + } + + break; + + case 'Text': + let c = -1; + while ((c = node.data.indexOf('{', c + 1)) !== -1) { + code.overwrite(a + c, a + c + 1, '{'); + } + break; + + case 'Attribute': + if (source[a] === ':') { + code.overwrite(a, a + 1, '{').appendLeft(b, '}'); + } + + break; + + case 'Spread': + code.remove(a, a + 1).remove(b - 1, b); + break; + + case 'EventHandler': + if (node.expression) { + const { name, parts } = flattenReference(node.expression.callee); + if (name === 'store') { + if (`$${parts[1].name}` in methods) { + console.error(`Not overwriting store method — $${parts[1].name} already exists on component`); + } else { + code.overwrite(node.expression.start, parts[1].start, '$'); + } + } + } + + break; + } + }, + + leave(node) { + + } + }); + + + + return code.toString(); +} \ No newline at end of file diff --git a/test/test.js b/test/test.js index 70e6c15..1b4497e 100644 --- a/test/test.js +++ b/test/test.js @@ -1,16 +1,21 @@ import * as fs from 'fs'; import { test } from 'tape-modern'; -import { upgradeTemplate } from '../src/index'; +import { v2, v3 } from '../src/index'; -fs.readdirSync('test/samples').forEach(dir => { - if (dir[0] === '.') return; +function testVersion(v, upgrader) { + fs.readdirSync(`test/v${v}/samples`).forEach(dir => { + if (dir[0] === '.') return; - test(dir, t => { - const source = fs.readFileSync(`test/samples/${dir}/input.html`, 'utf-8'); - const expected = fs.readFileSync(`test/samples/${dir}/output.html`, 'utf-8'); + test(dir, t => { + const source = fs.readFileSync(`test/v${v}/samples/${dir}/input.html`, 'utf-8'); + const expected = fs.readFileSync(`test/v${v}/samples/${dir}/output.html`, 'utf-8'); - const actual = upgradeTemplate(source); + const actual = upgrader(source); - t.equal(actual, expected); + t.equal(actual, expected); + }); }); -}); \ No newline at end of file +} + +testVersion(2, v2); +testVersion(3, v3); \ No newline at end of file diff --git a/test/samples/await-pending-then-catch/input.html b/test/v2/samples/await-pending-then-catch/input.html similarity index 100% rename from test/samples/await-pending-then-catch/input.html rename to test/v2/samples/await-pending-then-catch/input.html diff --git a/test/samples/await-pending-then-catch/output.html b/test/v2/samples/await-pending-then-catch/output.html similarity index 100% rename from test/samples/await-pending-then-catch/output.html rename to test/v2/samples/await-pending-then-catch/output.html diff --git a/test/samples/await-then-catch/input.html b/test/v2/samples/await-then-catch/input.html similarity index 100% rename from test/samples/await-then-catch/input.html rename to test/v2/samples/await-then-catch/input.html diff --git a/test/samples/await-then-catch/output.html b/test/v2/samples/await-then-catch/output.html similarity index 100% rename from test/samples/await-then-catch/output.html rename to test/v2/samples/await-then-catch/output.html diff --git a/test/samples/await-then/input.html b/test/v2/samples/await-then/input.html similarity index 100% rename from test/samples/await-then/input.html rename to test/v2/samples/await-then/input.html diff --git a/test/samples/await-then/output.html b/test/v2/samples/await-then/output.html similarity index 100% rename from test/samples/await-then/output.html rename to test/v2/samples/await-then/output.html diff --git a/test/samples/builtins-component-complex/input.html b/test/v2/samples/builtins-component-complex/input.html similarity index 100% rename from test/samples/builtins-component-complex/input.html rename to test/v2/samples/builtins-component-complex/input.html diff --git a/test/samples/builtins-component-complex/output.html b/test/v2/samples/builtins-component-complex/output.html similarity index 100% rename from test/samples/builtins-component-complex/output.html rename to test/v2/samples/builtins-component-complex/output.html diff --git a/test/samples/builtins-component/input.html b/test/v2/samples/builtins-component/input.html similarity index 100% rename from test/samples/builtins-component/input.html rename to test/v2/samples/builtins-component/input.html diff --git a/test/samples/builtins-component/output.html b/test/v2/samples/builtins-component/output.html similarity index 100% rename from test/samples/builtins-component/output.html rename to test/v2/samples/builtins-component/output.html diff --git a/test/samples/builtins-head/input.html b/test/v2/samples/builtins-head/input.html similarity index 100% rename from test/samples/builtins-head/input.html rename to test/v2/samples/builtins-head/input.html diff --git a/test/samples/builtins-head/output.html b/test/v2/samples/builtins-head/output.html similarity index 100% rename from test/samples/builtins-head/output.html rename to test/v2/samples/builtins-head/output.html diff --git a/test/samples/builtins-self/input.html b/test/v2/samples/builtins-self/input.html similarity index 100% rename from test/samples/builtins-self/input.html rename to test/v2/samples/builtins-self/input.html diff --git a/test/samples/builtins-self/output.html b/test/v2/samples/builtins-self/output.html similarity index 100% rename from test/samples/builtins-self/output.html rename to test/v2/samples/builtins-self/output.html diff --git a/test/samples/builtins-window/input.html b/test/v2/samples/builtins-window/input.html similarity index 100% rename from test/samples/builtins-window/input.html rename to test/v2/samples/builtins-window/input.html diff --git a/test/samples/builtins-window/output.html b/test/v2/samples/builtins-window/output.html similarity index 100% rename from test/samples/builtins-window/output.html rename to test/v2/samples/builtins-window/output.html diff --git a/test/samples/computed-after-style/input.html b/test/v2/samples/computed-after-style/input.html similarity index 100% rename from test/samples/computed-after-style/input.html rename to test/v2/samples/computed-after-style/input.html diff --git a/test/samples/computed-after-style/output.html b/test/v2/samples/computed-after-style/output.html similarity index 100% rename from test/samples/computed-after-style/output.html rename to test/v2/samples/computed-after-style/output.html diff --git a/test/samples/computed/input.html b/test/v2/samples/computed/input.html similarity index 100% rename from test/samples/computed/input.html rename to test/v2/samples/computed/input.html diff --git a/test/samples/computed/output.html b/test/v2/samples/computed/output.html similarity index 100% rename from test/samples/computed/output.html rename to test/v2/samples/computed/output.html diff --git a/test/samples/each-block-keyed/input.html b/test/v2/samples/each-block-keyed/input.html similarity index 100% rename from test/samples/each-block-keyed/input.html rename to test/v2/samples/each-block-keyed/input.html diff --git a/test/samples/each-block-keyed/output.html b/test/v2/samples/each-block-keyed/output.html similarity index 100% rename from test/samples/each-block-keyed/output.html rename to test/v2/samples/each-block-keyed/output.html diff --git a/test/samples/each-block/input.html b/test/v2/samples/each-block/input.html similarity index 100% rename from test/samples/each-block/input.html rename to test/v2/samples/each-block/input.html diff --git a/test/samples/each-block/output.html b/test/v2/samples/each-block/output.html similarity index 100% rename from test/samples/each-block/output.html rename to test/v2/samples/each-block/output.html diff --git a/test/samples/each-else/input.html b/test/v2/samples/each-else/input.html similarity index 100% rename from test/samples/each-else/input.html rename to test/v2/samples/each-else/input.html diff --git a/test/samples/each-else/output.html b/test/v2/samples/each-else/output.html similarity index 100% rename from test/samples/each-else/output.html rename to test/v2/samples/each-else/output.html diff --git a/test/samples/escape-braces/input.html b/test/v2/samples/escape-braces/input.html similarity index 100% rename from test/samples/escape-braces/input.html rename to test/v2/samples/escape-braces/input.html diff --git a/test/samples/escape-braces/output.html b/test/v2/samples/escape-braces/output.html similarity index 100% rename from test/samples/escape-braces/output.html rename to test/v2/samples/escape-braces/output.html diff --git a/test/samples/event-handler-shorthand/input.html b/test/v2/samples/event-handler-shorthand/input.html similarity index 100% rename from test/samples/event-handler-shorthand/input.html rename to test/v2/samples/event-handler-shorthand/input.html diff --git a/test/samples/event-handler-shorthand/output.html b/test/v2/samples/event-handler-shorthand/output.html similarity index 100% rename from test/samples/event-handler-shorthand/output.html rename to test/v2/samples/event-handler-shorthand/output.html diff --git a/test/samples/if-block-else/input.html b/test/v2/samples/if-block-else/input.html similarity index 100% rename from test/samples/if-block-else/input.html rename to test/v2/samples/if-block-else/input.html diff --git a/test/samples/if-block-else/output.html b/test/v2/samples/if-block-else/output.html similarity index 100% rename from test/samples/if-block-else/output.html rename to test/v2/samples/if-block-else/output.html diff --git a/test/samples/if-block-elseif/input.html b/test/v2/samples/if-block-elseif/input.html similarity index 100% rename from test/samples/if-block-elseif/input.html rename to test/v2/samples/if-block-elseif/input.html diff --git a/test/samples/if-block-elseif/output.html b/test/v2/samples/if-block-elseif/output.html similarity index 100% rename from test/samples/if-block-elseif/output.html rename to test/v2/samples/if-block-elseif/output.html diff --git a/test/samples/if-block/input.html b/test/v2/samples/if-block/input.html similarity index 100% rename from test/samples/if-block/input.html rename to test/v2/samples/if-block/input.html diff --git a/test/samples/if-block/output.html b/test/v2/samples/if-block/output.html similarity index 100% rename from test/samples/if-block/output.html rename to test/v2/samples/if-block/output.html diff --git a/test/samples/if-else-if/input.html b/test/v2/samples/if-else-if/input.html similarity index 100% rename from test/samples/if-else-if/input.html rename to test/v2/samples/if-else-if/input.html diff --git a/test/samples/if-else-if/output.html b/test/v2/samples/if-else-if/output.html similarity index 100% rename from test/samples/if-else-if/output.html rename to test/v2/samples/if-else-if/output.html diff --git a/test/samples/ignore-bad-css/input.html b/test/v2/samples/ignore-bad-css/input.html similarity index 100% rename from test/samples/ignore-bad-css/input.html rename to test/v2/samples/ignore-bad-css/input.html diff --git a/test/samples/ignore-bad-css/output.html b/test/v2/samples/ignore-bad-css/output.html similarity index 100% rename from test/samples/ignore-bad-css/output.html rename to test/v2/samples/ignore-bad-css/output.html diff --git a/test/samples/raw-tag/input.html b/test/v2/samples/raw-tag/input.html similarity index 100% rename from test/samples/raw-tag/input.html rename to test/v2/samples/raw-tag/input.html diff --git a/test/samples/raw-tag/output.html b/test/v2/samples/raw-tag/output.html similarity index 100% rename from test/samples/raw-tag/output.html rename to test/v2/samples/raw-tag/output.html diff --git a/test/samples/shorthand-properties/input.html b/test/v2/samples/shorthand-properties/input.html similarity index 100% rename from test/samples/shorthand-properties/input.html rename to test/v2/samples/shorthand-properties/input.html diff --git a/test/samples/shorthand-properties/output.html b/test/v2/samples/shorthand-properties/output.html similarity index 100% rename from test/samples/shorthand-properties/output.html rename to test/v2/samples/shorthand-properties/output.html diff --git a/test/samples/spread/input.html b/test/v2/samples/spread/input.html similarity index 100% rename from test/samples/spread/input.html rename to test/v2/samples/spread/input.html diff --git a/test/samples/spread/output.html b/test/v2/samples/spread/output.html similarity index 100% rename from test/samples/spread/output.html rename to test/v2/samples/spread/output.html diff --git a/test/samples/store-method/input.html b/test/v2/samples/store-method/input.html similarity index 100% rename from test/samples/store-method/input.html rename to test/v2/samples/store-method/input.html diff --git a/test/samples/store-method/output.html b/test/v2/samples/store-method/output.html similarity index 100% rename from test/samples/store-method/output.html rename to test/v2/samples/store-method/output.html diff --git a/test/samples/tag/input.html b/test/v2/samples/tag/input.html similarity index 100% rename from test/samples/tag/input.html rename to test/v2/samples/tag/input.html diff --git a/test/samples/tag/output.html b/test/v2/samples/tag/output.html similarity index 100% rename from test/samples/tag/output.html rename to test/v2/samples/tag/output.html diff --git a/test/samples/yield/input.html b/test/v2/samples/yield/input.html similarity index 100% rename from test/samples/yield/input.html rename to test/v2/samples/yield/input.html diff --git a/test/samples/yield/output.html b/test/v2/samples/yield/output.html similarity index 100% rename from test/samples/yield/output.html rename to test/v2/samples/yield/output.html diff --git a/test/v3/samples/script-less/input.html b/test/v3/samples/script-less/input.html new file mode 100644 index 0000000..543c605 --- /dev/null +++ b/test/v3/samples/script-less/input.html @@ -0,0 +1 @@ +

Hello {name}!

\ No newline at end of file diff --git a/test/v3/samples/script-less/output.html b/test/v3/samples/script-less/output.html new file mode 100644 index 0000000..543c605 --- /dev/null +++ b/test/v3/samples/script-less/output.html @@ -0,0 +1 @@ +

Hello {name}!

\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f4ca0cc..9a597a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -415,11 +415,16 @@ sourcemap-codec@^1.4.1: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz#c8fd92d91889e902a07aee392bdd2c5863958ba2" integrity sha512-hX1eNBNuilj8yfFnECh0DzLgwKpBLMIvmhgEhixXNui8lMLBInTI8Kyxt++RwJnMNu7cAUo635L2+N1TxMJCzA== -svelte@1: +"svelte-1@npm:svelte@1", svelte@1: version "1.64.1" resolved "https://registry.yarnpkg.com/svelte/-/svelte-1.64.1.tgz#03c97e204e0277c1430f429a6755ca425852e5de" integrity sha512-RsEAcVYF90Bq5y4Lgg56LyKiuxiyDTU+5TG2GM0ppa50z446de0vQgnA3eQTrgecPdRR89ZIqNqN1cgdAdO7wQ== +"svelte-2@npm:svelte@2": + version "2.15.1" + resolved "https://registry.yarnpkg.com/svelte/-/svelte-2.15.1.tgz#827da0505184af9529e325b8356c0ed4cba041cd" + integrity sha512-Mq9SJ9MXoNpjWLr+rK/xmDr9DT1unZxmVl4hq47qyU/Ksobp6CLEj0FDwLqa4b8/T8nViT7L1hBaDPfD6YNHbg== + tape-modern@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/tape-modern/-/tape-modern-1.1.1.tgz#32189f709740672dc4ca965489208caba983986e" From 50e9923ad74e4fcd3ef6e3cf5bb2b0b31ffc56f6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Nov 2018 17:48:08 -0500 Subject: [PATCH 02/52] make a start on v3 --- src/v3/index.js | 284 ++++++++++--------------- test/test.js | 2 +- test/v3/samples/data-arrow/input.html | 9 + test/v3/samples/data-arrow/output.html | 5 + test/v3/samples/data/input.html | 11 + test/v3/samples/data/output.html | 5 + 6 files changed, 144 insertions(+), 172 deletions(-) create mode 100644 test/v3/samples/data-arrow/input.html create mode 100644 test/v3/samples/data-arrow/output.html create mode 100644 test/v3/samples/data/input.html create mode 100644 test/v3/samples/data/output.html diff --git a/src/v3/index.js b/src/v3/index.js index e5e85c9..4b80052 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -1,4 +1,4 @@ -import * as svelte from 'svelte-2'; +import * as svelte from 'svelte'; import MagicString from 'magic-string'; import { walk, childKeys } from 'estree-walker'; @@ -8,221 +8,163 @@ import { walk, childKeys } from 'estree-walker'; childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; childKeys.Attribute = ['value']; -export function upgradeTemplate(source) { - const code = new MagicString(source); - const { ast } = svelte.compile(source); - - throw new Error('TODO'); - - function trimStart(node) { - let c = node.start; +function error(message, pos) { + const e = new Error(message); + e.pos = pos; - code.remove(c, c + 1); + // TODO add code frame - c = node.expression.end; - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - function trimEnd(node) { - let c = node.end; + throw e; +} - code.remove(c - 1, c); - - while (source[c - 1] !== '{') c -= 1; - code.remove(c - 1, c); - } +export function upgradeTemplate(source) { + const code = new MagicString(source); + const result = svelte.compile(source, { + generate: false + }); - function trim(node) { - trimStart(node); - trimEnd(node); - } + const indent = code.getIndentString(); const properties = {}; const methods = {}; - if (ast.js) { - const defaultExport = ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); + if (result.ast.js) { + const defaultValues = new Map(); + result.stats.props.forEach(prop => { + defaultValues.set(prop, undefined); + }); + + const defaultExport = result.ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); if (defaultExport) { + const blocks = []; + + // TODO set up indentExclusionRanges + defaultExport.declaration.properties.forEach(prop => { - properties[prop.key.name] = prop.value; + switch (prop.key.name) { + case 'data': + handleData(prop.value, defaultValues, code, blocks); + break; + + default: + throw new Error(`Not implemented: ${prop.key.name}`); + } }); - if (properties.computed) { - properties.computed.properties.forEach(prop => { - const { params } = prop.value; + let props = []; + for (const [key, value] of defaultValues) { + props.push(`export let ${key} = ${value};`) + } - if (prop.value.type === 'FunctionExpression') { - let a = prop.value.start; - if (!prop.method) a += 8; - while (source[a] !== '(') a += 1; + blocks.push(props.join('\n')); - let b = params[0].start; - code.overwrite(a, b, '({ '); + // if (properties.computed) { + // properties.computed.properties.forEach(prop => { + // const { params } = prop.value; - a = b = params[params.length - 1].end; - while (source[b] !== ')') b += 1; - code.overwrite(a, b + 1, ' })'); - } else if (prop.value.type === 'ArrowFunctionExpression') { - let a = prop.value.start; - let b = params[0].start; + // if (prop.value.type === 'FunctionExpression') { + // let a = prop.value.start; + // if (!prop.method) a += 8; + // while (source[a] !== '(') a += 1; - if (a !== b) code.remove(a, b); - code.prependRight(b, '({ '); + // let b = params[0].start; + // code.overwrite(a, b, '({ '); - a = b = params[params.length - 1].end; - while (source[b] !== '=') b += 1; + // a = b = params[params.length - 1].end; + // while (source[b] !== ')') b += 1; + // code.overwrite(a, b + 1, ' })'); + // } else if (prop.value.type === 'ArrowFunctionExpression') { + // let a = prop.value.start; + // let b = params[0].start; - if (a !== b) code.remove(a, b); - code.appendLeft(a, ' }) '); - } - }); - } + // if (a !== b) code.remove(a, b); + // code.prependRight(b, '({ '); - if (properties.methods) { - properties.methods.properties.forEach(prop => { - methods[prop.key.name] = prop.value; - }); - } + // a = b = params[params.length - 1].end; + // while (source[b] !== '=') b += 1; + + // if (a !== b) code.remove(a, b); + // code.appendLeft(a, ' }) '); + // } + // }); + // } + + // if (properties.methods) { + // properties.methods.properties.forEach(prop => { + // methods[prop.key.name] = prop.value; + // }); + // } + + code.overwrite(defaultExport.start, defaultExport.end, blocks.join('\n\n')); } + + code.appendLeft(result.ast.js.end, '\n\n'); + code.move(result.ast.js.start, result.ast.js.end, 0); } - walk(ast.html, { + walk(result.ast.html, { enter(node) { let a = node.start; let b = node.end; switch (node.type) { - case 'MustacheTag': - trimStart(node); - break; - - case 'RawMustacheTag': - code.overwrite(a + 1, node.expression.start, '@html ').remove(b - 2, b); - break; - - case 'AwaitBlock': - trim(node); - - if (node.pending.start !== null) { - let c = node.then.start; - code.overwrite(c + 1, c + 2, ':'); - - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - if (node.catch.start !== null) { - let c = node.catch.start; - code.overwrite(c + 1, c + 2, ':'); - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - break; - - case 'IfBlock': - case 'EachBlock': - if (!node.skip) trim(node); - - if (node.else) { - let c = node.children[node.children.length - 1].end; - while (source[c] !== '{') c += 1; - code.overwrite(c + 1, c + 2, ':'); - - if (hasElseIf(source, node)) { - c = node.else.children[0].expression.end; - node.else.children[0].skip = true; - } - - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - if (node.key) { - let a = node.expression.end; - while (source[a] !== '@') a += 1; - code.overwrite(a, a + 1, `(${node.context}.`); - - while (!/\w/.test(source[a])) a += 1; - while (/\w/.test(source[a])) a += 1; - code.appendLeft(a, ')'); - } - - break; + } + }, - case 'Element': - case 'Window': - case 'Head': - if (node.name === 'slot' && /{{\s*yield\s*}}/.test(source.slice(a, b))) { - code.overwrite(a, b, ''); - } + leave(node) { - else if (node.name[0] === ':') { - const name = `svelte:${node.name[1].toLowerCase()}`; - code.overwrite(a + 1, a + 3, name); + } + }); - while (source[b - 1] !== '<') b -= 1; - if (source[b] === '/') { - code.overwrite(b + 1, b + 3, name); - } - } - if (node.name === ':Component') { - a = node.expression.start; - while (source[a - 1] !== '{') a -= 1; - b = node.expression.end; - while (source[b] !== '}') b += 1; + return code.toString().trim(); +} - const shouldQuote = /\s/.test(source.slice(a, b)); - if (shouldQuote) code.prependRight(a - 1, '"').appendLeft(b + 1, '"'); - code.prependRight(a - 1, 'this=') - } +function handleData(node, props, code, blocks) { + if (!/FunctionExpression/.test(node.type)) { + error(`can only convert 'data' if it is a function expression or arrow function expression`, node.start); + } - break; + let returned; - case 'Text': - let c = -1; - while ((c = node.data.indexOf('{', c + 1)) !== -1) { - code.overwrite(a + c, a + c + 1, '{'); + if (node.body.type === 'BlockStatement') { + walk(node.body, { + enter(child, parent) { + if (child.type === 'ReturnStatement') { + if (parent !== node.body) { + console.log({ parent }); + error(`can only convert data with a top-level return statement`, child.start); } - break; - case 'Attribute': - if (source[a] === ':') { - code.overwrite(a, a + 1, '{').appendLeft(b, '}'); + if (returned) { + error(`duplicate return statement`, child.start); } - break; - - case 'Spread': - code.remove(a, a + 1).remove(b - 1, b); - break; - - case 'EventHandler': - if (node.expression) { - const { name, parts } = flattenReference(node.expression.callee); - if (name === 'store') { - if (`$${parts[1].name}` in methods) { - console.error(`Not overwriting store method — $${parts[1].name} already exists on component`); - } else { - code.overwrite(node.expression.start, parts[1].start, '$'); - } - } + const index = node.body.body.indexOf(child); + if (index !== 0) { + throw new Error(`TODO handle statements before return`); } - break; + returned = child.argument; + } } - }, - - leave(node) { + }); + if (!returned) { + error(`missing return statement`, child.start); } - }); - + } else { + returned = node.body; + while (returned.type === 'ParenthesizedExpression') returned = returned.expression; + if (returned.type !== 'ObjectExpression') { + error(`can only convert an object literal`, returned.start); + } + } - return code.toString(); + returned.properties.forEach(prop => { + props.set(prop.key.name, code.original.slice(prop.value.start, prop.value.end)); + }); } \ No newline at end of file diff --git a/test/test.js b/test/test.js index 1b4497e..cddafdd 100644 --- a/test/test.js +++ b/test/test.js @@ -17,5 +17,5 @@ function testVersion(v, upgrader) { }); } -testVersion(2, v2); +// testVersion(2, v2); testVersion(3, v3); \ No newline at end of file diff --git a/test/v3/samples/data-arrow/input.html b/test/v3/samples/data-arrow/input.html new file mode 100644 index 0000000..7e346c8 --- /dev/null +++ b/test/v3/samples/data-arrow/input.html @@ -0,0 +1,9 @@ +

Hello {name}!

+ + \ No newline at end of file diff --git a/test/v3/samples/data-arrow/output.html b/test/v3/samples/data-arrow/output.html new file mode 100644 index 0000000..41e3ddf --- /dev/null +++ b/test/v3/samples/data-arrow/output.html @@ -0,0 +1,5 @@ + + +

Hello {name}!

\ No newline at end of file diff --git a/test/v3/samples/data/input.html b/test/v3/samples/data/input.html new file mode 100644 index 0000000..6f53028 --- /dev/null +++ b/test/v3/samples/data/input.html @@ -0,0 +1,11 @@ +

Hello {name}!

+ + \ No newline at end of file diff --git a/test/v3/samples/data/output.html b/test/v3/samples/data/output.html new file mode 100644 index 0000000..41e3ddf --- /dev/null +++ b/test/v3/samples/data/output.html @@ -0,0 +1,5 @@ + + +

Hello {name}!

\ No newline at end of file From 06d2833f979a0c55d3c29666e14ab61bc16f24a5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Nov 2018 18:03:00 -0500 Subject: [PATCH 03/52] switch to kleur --- package.json | 4 +-- src/cli.js | 16 +++++------ yarn.lock | 79 +++++++++++++++++++++++++--------------------------- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/package.json b/package.json index 5628eb8..18923f9 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,11 @@ "dependencies": { "estree-walker": "^0.5.2", "glob": "^7.1.3", + "kleur": "^2.0.2", "magic-string": "^0.25.1", "prompts": "^1.2.0", "sade": "^1.4.1", "svelte-1": "npm:svelte@1", - "svelte-2": "npm:svelte@2", - "turbocolor": "^2.6.1" + "svelte-2": "npm:svelte@2" } } diff --git a/src/cli.js b/src/cli.js index eb8bfb6..e7d533c 100644 --- a/src/cli.js +++ b/src/cli.js @@ -3,7 +3,7 @@ import path from 'path'; import sade from 'sade'; import glob from 'glob'; import prompts from 'prompts'; -import tc from 'turbocolor'; +import c from 'kleur'; import * as pkg from '../package.json'; const prog = sade('svelte-upgrade').version(pkg.version); @@ -49,8 +49,8 @@ function mkdirp(dir) { if (!opts.force) { const existing = output.filter(file => fs.existsSync(file)); if (existing.length > 0) { - console.error(tc.cyan(`This will overwrite the following files:`)); - console.error(tc.gray(existing.join('\n'))) + console.error(c.cyan(`This will overwrite the following files:`)); + console.error(c.gray(existing.join('\n'))) const response = await prompts({ type: 'confirm', @@ -60,7 +60,7 @@ function mkdirp(dir) { }); if (response.value === false) { - console.error(tc.cyan('Aborted')); + console.error(c.cyan('Aborted')); return; } } @@ -75,8 +75,8 @@ function mkdirp(dir) { mkdirp(path.dirname(dest)); fs.writeFileSync(dest, upgraded); } catch (err) { - console.error(tc.bold.red(`Error transforming ${src}:`)); - console.error(tc.red(err.message)); + console.error(c.bold.red(`Error transforming ${src}:`)); + console.error(c.red(err.message)); if (err.frame) { console.error(err.frame); @@ -84,9 +84,9 @@ function mkdirp(dir) { } }); - console.error(tc.cyan(`Wrote ${output.length} ${output.length === 1 ? 'file' : 'files'}`)) + console.error(c.cyan(`Wrote ${output.length} ${output.length === 1 ? 'file' : 'files'}`)) } catch (err) { - console.error(tc.red(err.message)); + console.error(c.red(err.message)); } }); }); diff --git a/yarn.lock b/yarn.lock index 9a597a9..c45823e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -93,13 +93,13 @@ filename-regex@^2.0.0: integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - integrity sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM= + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" @@ -201,12 +201,10 @@ is-number@^2.1.0: dependencies: kind-of "^3.0.2" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== is-posix-bracket@^0.1.0: version "0.1.1" @@ -237,14 +235,12 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.1.5" -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" +kind-of@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== -kleur@^2.0.1: +kleur@^2.0.1, kleur@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== @@ -256,6 +252,11 @@ magic-string@^0.25.1: dependencies: sourcemap-codec "^1.4.1" +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= + micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" @@ -283,9 +284,9 @@ minimatch@^3.0.4: brace-expansion "^1.1.7" mri@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.0.tgz#5c0a3f29c8ccffbbb1ec941dcec09d71fa32f36a" - integrity sha1-XAo/KcjM/7ux7JQdzsCdcfoy82o= + version "1.1.1" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.1.tgz#85aa26d3daeeeedf80dc5984af95cc5ca5cad9f1" + integrity sha1-haom09ru7t+A3FmEr5XMXKXK2fE= normalize-path@^2.0.1: version "2.1.1" @@ -344,13 +345,14 @@ prompts@^1.2.0: kleur "^2.0.1" sisteransi "^1.0.0" -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - integrity sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how== +randomatic@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" + integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" regex-cache@^0.4.2: version "0.4.4" @@ -365,9 +367,9 @@ remove-trailing-separator@^1.0.1: integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo= + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== repeat-string@^1.5.2: version "1.6.1" @@ -411,30 +413,25 @@ sisteransi@^1.0.0: integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ== sourcemap-codec@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz#c8fd92d91889e902a07aee392bdd2c5863958ba2" - integrity sha512-hX1eNBNuilj8yfFnECh0DzLgwKpBLMIvmhgEhixXNui8lMLBInTI8Kyxt++RwJnMNu7cAUo635L2+N1TxMJCzA== + version "1.4.3" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.3.tgz#0ba615b73ec35112f63c2f2d9e7c3f87282b0e33" + integrity sha512-vFrY/x/NdsD7Yc8mpTJXuao9S8lq08Z/kOITHz6b7YbfI9xL8Spe5EvSQUHOI7SbpY8bRPr0U3kKSsPuqEGSfA== -"svelte-1@npm:svelte@1", svelte@1: +"svelte-1@npm:svelte@1": version "1.64.1" resolved "https://registry.yarnpkg.com/svelte/-/svelte-1.64.1.tgz#03c97e204e0277c1430f429a6755ca425852e5de" integrity sha512-RsEAcVYF90Bq5y4Lgg56LyKiuxiyDTU+5TG2GM0ppa50z446de0vQgnA3eQTrgecPdRR89ZIqNqN1cgdAdO7wQ== "svelte-2@npm:svelte@2": - version "2.15.1" - resolved "https://registry.yarnpkg.com/svelte/-/svelte-2.15.1.tgz#827da0505184af9529e325b8356c0ed4cba041cd" - integrity sha512-Mq9SJ9MXoNpjWLr+rK/xmDr9DT1unZxmVl4hq47qyU/Ksobp6CLEj0FDwLqa4b8/T8nViT7L1hBaDPfD6YNHbg== + version "2.15.2" + resolved "https://registry.yarnpkg.com/svelte/-/svelte-2.15.2.tgz#6aaf4a10307409099ec30dd7c73f746fef32c949" + integrity sha512-l/YAQQ/ArmUTlEdspyLS7viFtKX0ZRKVmYeizAlvFoJ4vKos+7/aBD8xWxQTUd8EPh1JD95DVy7NK3h8+ITlxw== tape-modern@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/tape-modern/-/tape-modern-1.1.1.tgz#32189f709740672dc4ca965489208caba983986e" integrity sha512-sb1PNwSlphD2t5z9diUO7dd+r25sfdw94wD3yyi1Dcmu7oJ6wKMGdZ/pB6OZ0jqm0oAEESL4iOHeiCqnHhc1dg== -turbocolor@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/turbocolor/-/turbocolor-2.6.1.tgz#1b47dcc0e0e5171f57d954351fb80a5088a8a921" - integrity sha512-0pTvPfKBIasx7C4bEorJY9I3SNbkyQrFeRxK8iMoarEHZLQLOhPungv9t/7SSfKLJdErhv4dC2GKmaNGURnk4A== - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 27237d6aa87b9c82990ec73c6155f43ffc1af2b2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Nov 2018 18:04:26 -0500 Subject: [PATCH 04/52] fix --- src/v3/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v3/index.js b/src/v3/index.js index 4b80052..b160d4d 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -1,4 +1,4 @@ -import * as svelte from 'svelte'; +import * as svelte from 'svelte-2'; import MagicString from 'magic-string'; import { walk, childKeys } from 'estree-walker'; From 1e8608c26c319abad4bd13daaf0dab4fa789c462 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Nov 2018 18:16:45 -0500 Subject: [PATCH 05/52] tag and namespace --- rollup.config.js | 3 +- src/v3/index.js | 43 +++++++++++++++++++++------ test/v3/samples/namespace/input.html | 7 +++++ test/v3/samples/namespace/output.html | 3 ++ test/v3/samples/tag/input.html | 7 +++++ test/v3/samples/tag/output.html | 3 ++ 6 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 test/v3/samples/namespace/input.html create mode 100644 test/v3/samples/namespace/output.html create mode 100644 test/v3/samples/tag/input.html create mode 100644 test/v3/samples/tag/output.html diff --git a/rollup.config.js b/rollup.config.js index a00af87..4073847 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -5,10 +5,9 @@ export default { input: ['src/index.js', 'src/cli.js'], output: { dir: 'dist', - format: 'cjs' + format: 'cjs' }, experimentalCodeSplitting: true, - experimentalDynamicImport: true, external: Object.keys(pkg.dependencies), plugins: [ json() diff --git a/src/v3/index.js b/src/v3/index.js index b160d4d..ebce578 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -28,16 +28,19 @@ export function upgradeTemplate(source) { const properties = {}; const methods = {}; + let tag; + let namespace; + if (result.ast.js) { const defaultValues = new Map(); result.stats.props.forEach(prop => { defaultValues.set(prop, undefined); }); + const blocks = []; const defaultExport = result.ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); - if (defaultExport) { - const blocks = []; + if (defaultExport) { // TODO set up indentExclusionRanges defaultExport.declaration.properties.forEach(prop => { @@ -46,6 +49,14 @@ export function upgradeTemplate(source) { handleData(prop.value, defaultValues, code, blocks); break; + case 'tag': + tag = prop.value.value; + break; + + case 'namespace': + namespace = prop.value.value; + break; + default: throw new Error(`Not implemented: ${prop.key.name}`); } @@ -56,7 +67,7 @@ export function upgradeTemplate(source) { props.push(`export let ${key} = ${value};`) } - blocks.push(props.join('\n')); + if (props.length > 0) blocks.push(props.join('\n')); // if (properties.computed) { // properties.computed.properties.forEach(prop => { @@ -99,7 +110,17 @@ export function upgradeTemplate(source) { } code.appendLeft(result.ast.js.end, '\n\n'); - code.move(result.ast.js.start, result.ast.js.end, 0); + + const needsScript = ( + blocks.length > 0 || + !!result.ast.js.content.body.find(node => node !== defaultExport) + ); + + if (needsScript) { + code.move(result.ast.js.start, result.ast.js.end, 0); + } else { + code.remove(result.ast.js.start, result.ast.js.end); + } } walk(result.ast.html, { @@ -110,16 +131,20 @@ export function upgradeTemplate(source) { switch (node.type) { } - }, - - leave(node) { - } }); + let upgraded = code.toString().trim(); + + if (tag || namespace) { // TODO or bindings + const attributes = []; + if (tag) attributes.push(`tag="${tag}"`); + if (namespace) attributes.push(`namespace="${namespace}"`); + upgraded = `\n\n${upgraded}`; + } - return code.toString().trim(); + return upgraded; } function handleData(node, props, code, blocks) { diff --git a/test/v3/samples/namespace/input.html b/test/v3/samples/namespace/input.html new file mode 100644 index 0000000..6db8011 --- /dev/null +++ b/test/v3/samples/namespace/input.html @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/namespace/output.html b/test/v3/samples/namespace/output.html new file mode 100644 index 0000000..923d629 --- /dev/null +++ b/test/v3/samples/namespace/output.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/tag/input.html b/test/v3/samples/tag/input.html new file mode 100644 index 0000000..60b7ae5 --- /dev/null +++ b/test/v3/samples/tag/input.html @@ -0,0 +1,7 @@ +

I am a custom element

+ + \ No newline at end of file diff --git a/test/v3/samples/tag/output.html b/test/v3/samples/tag/output.html new file mode 100644 index 0000000..900cccb --- /dev/null +++ b/test/v3/samples/tag/output.html @@ -0,0 +1,3 @@ + + +

I am a custom element

\ No newline at end of file From f15d6ef46a3dc3a6e6cd11b675d8d393e864da8f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Nov 2018 19:12:06 -0500 Subject: [PATCH 06/52] ignore props whose value matches the name --- package.json | 1 + src/v3/findDeclarations.js | 29 +++++++++++++++++++++ src/v3/index.js | 31 ++++++++++++++++++++--- test/test.js | 16 ++++++++++-- test/v3/samples/data-imported/input.html | 13 ++++++++++ test/v3/samples/data-imported/output.html | 5 ++++ yarn.lock | 2 +- 7 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 src/v3/findDeclarations.js create mode 100644 test/v3/samples/data-imported/input.html create mode 100644 test/v3/samples/data-imported/output.html diff --git a/package.json b/package.json index 18923f9..45edfb9 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "magic-string": "^0.25.1", "prompts": "^1.2.0", "sade": "^1.4.1", + "svelte": "^2.15.2", "svelte-1": "npm:svelte@1", "svelte-2": "npm:svelte@2" } diff --git a/src/v3/findDeclarations.js b/src/v3/findDeclarations.js new file mode 100644 index 0000000..7adb0ad --- /dev/null +++ b/src/v3/findDeclarations.js @@ -0,0 +1,29 @@ +import { walk } from 'estree-walker'; + +export function findDeclarations(body) { + const declarations = new Set(); + + walk(body, { + enter(node, parent) { + if (node.type === 'ImportDeclaration') { + node.specifiers.forEach(specifier => { + declarations.add(specifier.local.name); + }); + } + + else if (node.type === 'ClassDeclaration') { + + } + + else if (node.type === 'FunctionDeclaration') { + + } + + else if (node.type === 'VariableDeclaration') { + + } + } + }); + + return declarations; +} \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index ebce578..acba619 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -1,6 +1,7 @@ -import * as svelte from 'svelte-2'; +import * as svelte from 'svelte'; import MagicString from 'magic-string'; import { walk, childKeys } from 'estree-walker'; +import { findDeclarations } from './findDeclarations'; // We need to tell estree-walker that it should always // look for an `else` block, otherwise it might get @@ -32,13 +33,17 @@ export function upgradeTemplate(source) { let namespace; if (result.ast.js) { + const { body } = result.ast.js.content; + const defaultValues = new Map(); result.stats.props.forEach(prop => { defaultValues.set(prop, undefined); }); const blocks = []; - const defaultExport = result.ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); + const defaultExport = body.find(node => node.type === 'ExportDefaultDeclaration'); + + const declarations = findDeclarations(body); if (defaultExport) { // TODO set up indentExclusionRanges @@ -64,6 +69,7 @@ export function upgradeTemplate(source) { let props = []; for (const [key, value] of defaultValues) { + if (key === value) continue; props.push(`export let ${key} = ${value};`) } @@ -113,10 +119,28 @@ export function upgradeTemplate(source) { const needsScript = ( blocks.length > 0 || - !!result.ast.js.content.body.find(node => node !== defaultExport) + !!body.find(node => node !== defaultExport) ); if (needsScript) { + if (blocks.length === 0 && defaultExport) { + const index = body.indexOf(defaultExport); + + let a = defaultExport.start; + let b = defaultExport.end; + + // need to remove whitespace around the default export + if (index === 0) { + throw new Error(`TODO remove default export from start`); + } else if (index === body.length - 1) { + while (/\s/.test(source[a - 1])) a -= 1; + } else { + throw new Error(`TODO remove default export from middle`); + } + + code.remove(a, b); + } + code.move(result.ast.js.start, result.ast.js.end, 0); } else { code.remove(result.ast.js.start, result.ast.js.end); @@ -159,7 +183,6 @@ function handleData(node, props, code, blocks) { enter(child, parent) { if (child.type === 'ReturnStatement') { if (parent !== node.body) { - console.log({ parent }); error(`can only convert data with a top-level return statement`, child.start); } diff --git a/test/test.js b/test/test.js index cddafdd..bb87858 100644 --- a/test/test.js +++ b/test/test.js @@ -2,10 +2,22 @@ import * as fs from 'fs'; import { test } from 'tape-modern'; import { v2, v3 } from '../src/index'; +const args = process.argv.slice(2); + +const versions = new Set(args.filter(x => /^v\d$/.test(x))); +if (versions.size === 0) { + versions.add('v2'); + versions.add('v3'); +} + +const tests = new Set(args.filter(x => !/^v\d$/.test(x))); + function testVersion(v, upgrader) { fs.readdirSync(`test/v${v}/samples`).forEach(dir => { if (dir[0] === '.') return; + if (tests.size && !tests.has(dir)) return; + test(dir, t => { const source = fs.readFileSync(`test/v${v}/samples/${dir}/input.html`, 'utf-8'); const expected = fs.readFileSync(`test/v${v}/samples/${dir}/output.html`, 'utf-8'); @@ -17,5 +29,5 @@ function testVersion(v, upgrader) { }); } -// testVersion(2, v2); -testVersion(3, v3); \ No newline at end of file +if (versions.has('v2')) testVersion(2, v2); +if (versions.has('v3')) testVersion(3, v3); \ No newline at end of file diff --git a/test/v3/samples/data-imported/input.html b/test/v3/samples/data-imported/input.html new file mode 100644 index 0000000..b8369cb --- /dev/null +++ b/test/v3/samples/data-imported/input.html @@ -0,0 +1,13 @@ +

The answer is {answer}

+ + \ No newline at end of file diff --git a/test/v3/samples/data-imported/output.html b/test/v3/samples/data-imported/output.html new file mode 100644 index 0000000..b86c40f --- /dev/null +++ b/test/v3/samples/data-imported/output.html @@ -0,0 +1,5 @@ + + +

The answer is {answer}

\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c45823e..1dbe103 100644 --- a/yarn.lock +++ b/yarn.lock @@ -422,7 +422,7 @@ sourcemap-codec@^1.4.1: resolved "https://registry.yarnpkg.com/svelte/-/svelte-1.64.1.tgz#03c97e204e0277c1430f429a6755ca425852e5de" integrity sha512-RsEAcVYF90Bq5y4Lgg56LyKiuxiyDTU+5TG2GM0ppa50z446de0vQgnA3eQTrgecPdRR89ZIqNqN1cgdAdO7wQ== -"svelte-2@npm:svelte@2": +"svelte-2@npm:svelte@2", svelte@^2.15.2: version "2.15.2" resolved "https://registry.yarnpkg.com/svelte/-/svelte-2.15.2.tgz#6aaf4a10307409099ec30dd7c73f746fef32c949" integrity sha512-l/YAQQ/ArmUTlEdspyLS7viFtKX0ZRKVmYeizAlvFoJ4vKos+7/aBD8xWxQTUd8EPh1JD95DVy7NK3h8+ITlxw== From 553682db4891e4fc77d776229e5a178398c95a69 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Nov 2018 19:34:13 -0500 Subject: [PATCH 07/52] components --- src/v3/index.js | 27 +++++++++++++++++++ test/v3/samples/component-renamed/input.html | 11 ++++++++ test/v3/samples/component-renamed/output.html | 7 +++++ .../v3/samples/component-shorthand/input.html | 9 +++++++ .../samples/component-shorthand/output.html | 5 ++++ test/v3/samples/component/input.html | 11 ++++++++ test/v3/samples/component/output.html | 5 ++++ 7 files changed, 75 insertions(+) create mode 100644 test/v3/samples/component-renamed/input.html create mode 100644 test/v3/samples/component-renamed/output.html create mode 100644 test/v3/samples/component-shorthand/input.html create mode 100644 test/v3/samples/component-shorthand/output.html create mode 100644 test/v3/samples/component/input.html create mode 100644 test/v3/samples/component/output.html diff --git a/src/v3/index.js b/src/v3/index.js index acba619..564690c 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -41,6 +41,7 @@ export function upgradeTemplate(source) { }); const blocks = []; + const imports = []; const defaultExport = body.find(node => node.type === 'ExportDefaultDeclaration'); const declarations = findDeclarations(body); @@ -50,6 +51,10 @@ export function upgradeTemplate(source) { defaultExport.declaration.properties.forEach(prop => { switch (prop.key.name) { + case 'components': + handleComponents(prop.value, declarations, blocks, imports); + break; + case 'data': handleData(prop.value, defaultValues, code, blocks); break; @@ -171,6 +176,28 @@ export function upgradeTemplate(source) { return upgraded; } +function handleComponents(node, declarations, blocks, imports) { + const statements = []; + + node.properties.forEach(component => { + if (component.value.type === 'Literal') { + statements.push(`import ${component.key.name} from '${component.value.value}';`); + } else { + if (component.value.name !== component.key.name) { + if (declarations.has(component.key.name)) { + error(`component name conflicts with existing declaration`, component.start); + } + + statements.push(`const ${component.key.name} = ${component.value.name};`); + } + } + }); + + if (statements.length > 0) { + blocks.push(statements.join('\n')); + } +} + function handleData(node, props, code, blocks) { if (!/FunctionExpression/.test(node.type)) { error(`can only convert 'data' if it is a function expression or arrow function expression`, node.start); diff --git a/test/v3/samples/component-renamed/input.html b/test/v3/samples/component-renamed/input.html new file mode 100644 index 0000000..9b7c300 --- /dev/null +++ b/test/v3/samples/component-renamed/input.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/component-renamed/output.html b/test/v3/samples/component-renamed/output.html new file mode 100644 index 0000000..2a93ca7 --- /dev/null +++ b/test/v3/samples/component-renamed/output.html @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/component-shorthand/input.html b/test/v3/samples/component-shorthand/input.html new file mode 100644 index 0000000..b5c0a8b --- /dev/null +++ b/test/v3/samples/component-shorthand/input.html @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/component-shorthand/output.html b/test/v3/samples/component-shorthand/output.html new file mode 100644 index 0000000..583835f --- /dev/null +++ b/test/v3/samples/component-shorthand/output.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/component/input.html b/test/v3/samples/component/input.html new file mode 100644 index 0000000..c669ca0 --- /dev/null +++ b/test/v3/samples/component/input.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/component/output.html b/test/v3/samples/component/output.html new file mode 100644 index 0000000..583835f --- /dev/null +++ b/test/v3/samples/component/output.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file From 45c8a1cb35c2c50c4ce2ae750f66db49d086abe2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 4 Nov 2018 20:52:23 -0500 Subject: [PATCH 08/52] handle oncreate --- src/v3/handlers/components.js | 21 +++ src/v3/handlers/data.js | 47 ++++++ src/v3/handlers/oncreate.js | 12 ++ src/v3/index.js | 181 ++++++----------------- src/v3/{findDeclarations.js => utils.js} | 11 +- test/v3/samples/oncreate/input.html | 9 ++ test/v3/samples/oncreate/output.html | 9 ++ 7 files changed, 153 insertions(+), 137 deletions(-) create mode 100644 src/v3/handlers/components.js create mode 100644 src/v3/handlers/data.js create mode 100644 src/v3/handlers/oncreate.js rename src/v3/{findDeclarations.js => utils.js} (73%) create mode 100644 test/v3/samples/oncreate/input.html create mode 100644 test/v3/samples/oncreate/output.html diff --git a/src/v3/handlers/components.js b/src/v3/handlers/components.js new file mode 100644 index 0000000..c889ba0 --- /dev/null +++ b/src/v3/handlers/components.js @@ -0,0 +1,21 @@ +export default function handleComponents(node, declarations, blocks, imports) { + const statements = []; + + node.properties.forEach(component => { + if (component.value.type === 'Literal') { + statements.push(`import ${component.key.name} from '${component.value.value}';`); + } else { + if (component.value.name !== component.key.name) { + if (declarations.has(component.key.name)) { + error(`component name conflicts with existing declaration`, component.start); + } + + statements.push(`const ${component.key.name} = ${component.value.name};`); + } + } + }); + + if (statements.length > 0) { + blocks.push(statements.join('\n')); + } +} \ No newline at end of file diff --git a/src/v3/handlers/data.js b/src/v3/handlers/data.js new file mode 100644 index 0000000..79d29ad --- /dev/null +++ b/src/v3/handlers/data.js @@ -0,0 +1,47 @@ +import { walk } from 'estree-walker'; + +export default function handle_data(node, props, code, blocks) { + if (!/FunctionExpression/.test(node.type)) { + error(`can only convert 'data' if it is a function expression or arrow function expression`, node.start); + } + + let returned; + + if (node.body.type === 'BlockStatement') { + walk(node.body, { + enter(child, parent) { + if (child.type === 'ReturnStatement') { + if (parent !== node.body) { + error(`can only convert data with a top-level return statement`, child.start); + } + + if (returned) { + error(`duplicate return statement`, child.start); + } + + const index = node.body.body.indexOf(child); + if (index !== 0) { + throw new Error(`TODO handle statements before return`); + } + + returned = child.argument; + } + } + }); + + if (!returned) { + error(`missing return statement`, child.start); + } + } else { + returned = node.body; + while (returned.type === 'ParenthesizedExpression') returned = returned.expression; + + if (returned.type !== 'ObjectExpression') { + error(`can only convert an object literal`, returned.start); + } + } + + returned.properties.forEach(prop => { + props.set(prop.key.name, code.original.slice(prop.value.start, prop.value.end)); + }); +} \ No newline at end of file diff --git a/src/v3/handlers/oncreate.js b/src/v3/handlers/oncreate.js new file mode 100644 index 0000000..9f1645a --- /dev/null +++ b/src/v3/handlers/oncreate.js @@ -0,0 +1,12 @@ +import { error } from '../utils.js'; + +export default function handle_oncreate(node, code, blocks, indent_regex) { + if (node.type === 'FunctionExpression') { + const body = code.slice(node.body.start, node.body.end).replace(indent_regex, ''); + blocks.push(`onmount(() => ${body});`); + } + + else { + throw new Error(`TODO non-function-expression oncreate`); + } +} \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 564690c..0acbe47 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -1,7 +1,10 @@ import * as svelte from 'svelte'; import MagicString from 'magic-string'; import { walk, childKeys } from 'estree-walker'; -import { findDeclarations } from './findDeclarations'; +import handle_components from './handlers/components.js'; +import handle_data from './handlers/data.js'; +import handle_oncreate from './handlers/oncreate.js'; +import { error, find_declarations } from './utils.js'; // We need to tell estree-walker that it should always // look for an `else` block, otherwise it might get @@ -9,15 +12,6 @@ import { findDeclarations } from './findDeclarations'; childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; childKeys.Attribute = ['value']; -function error(message, pos) { - const e = new Error(message); - e.pos = pos; - - // TODO add code frame - - throw e; -} - export function upgradeTemplate(source) { const code = new MagicString(source); const result = svelte.compile(source, { @@ -25,40 +19,45 @@ export function upgradeTemplate(source) { }); const indent = code.getIndentString(); - - const properties = {}; - const methods = {}; + const indent_regex = new RegExp(`^${indent}`, 'gm'); let tag; let namespace; + let script_contents; if (result.ast.js) { const { body } = result.ast.js.content; - const defaultValues = new Map(); + const default_values = new Map(); result.stats.props.forEach(prop => { - defaultValues.set(prop, undefined); + default_values.set(prop, undefined); }); + const lifecycle_functions = new Set(); + const blocks = []; const imports = []; - const defaultExport = body.find(node => node.type === 'ExportDefaultDeclaration'); + const default_export = body.find(node => node.type === 'ExportDefaultDeclaration'); - const declarations = findDeclarations(body); + const declarations = find_declarations(body); - if (defaultExport) { + if (default_export) { // TODO set up indentExclusionRanges - defaultExport.declaration.properties.forEach(prop => { + default_export.declaration.properties.forEach(prop => { switch (prop.key.name) { case 'components': - handleComponents(prop.value, declarations, blocks, imports); + handle_components(prop.value, declarations, blocks, imports); break; case 'data': - handleData(prop.value, defaultValues, code, blocks); + handle_data(prop.value, default_values, code, blocks); break; + case 'oncreate': case 'onrender': + lifecycle_functions.add('onmount'); + handle_oncreate(prop.value, code, blocks, indent_regex); + case 'tag': tag = prop.value.value; break; @@ -73,66 +72,29 @@ export function upgradeTemplate(source) { }); let props = []; - for (const [key, value] of defaultValues) { + for (const [key, value] of default_values) { if (key === value) continue; props.push(`export let ${key} = ${value};`) } if (props.length > 0) blocks.push(props.join('\n')); - // if (properties.computed) { - // properties.computed.properties.forEach(prop => { - // const { params } = prop.value; - - // if (prop.value.type === 'FunctionExpression') { - // let a = prop.value.start; - // if (!prop.method) a += 8; - // while (source[a] !== '(') a += 1; - - // let b = params[0].start; - // code.overwrite(a, b, '({ '); - - // a = b = params[params.length - 1].end; - // while (source[b] !== ')') b += 1; - // code.overwrite(a, b + 1, ' })'); - // } else if (prop.value.type === 'ArrowFunctionExpression') { - // let a = prop.value.start; - // let b = params[0].start; - - // if (a !== b) code.remove(a, b); - // code.prependRight(b, '({ '); - - // a = b = params[params.length - 1].end; - // while (source[b] !== '=') b += 1; - - // if (a !== b) code.remove(a, b); - // code.appendLeft(a, ' }) '); - // } - // }); - // } - - // if (properties.methods) { - // properties.methods.properties.forEach(prop => { - // methods[prop.key.name] = prop.value; - // }); - // } - - code.overwrite(defaultExport.start, defaultExport.end, blocks.join('\n\n')); + code.overwrite(default_export.start, default_export.end, blocks.join('\n\n')); } code.appendLeft(result.ast.js.end, '\n\n'); - const needsScript = ( + const needs_script = ( blocks.length > 0 || - !!body.find(node => node !== defaultExport) + !!body.find(node => node !== default_export) ); - if (needsScript) { - if (blocks.length === 0 && defaultExport) { - const index = body.indexOf(defaultExport); + if (needs_script) { + if (blocks.length === 0 && default_export) { + const index = body.indexOf(default_export); - let a = defaultExport.start; - let b = defaultExport.end; + let a = default_export.start; + let b = default_export.end; // need to remove whitespace around the default export if (index === 0) { @@ -146,10 +108,21 @@ export function upgradeTemplate(source) { code.remove(a, b); } + script_contents = code.slice(result.ast.js.content.start, result.ast.js.content.end); + code.move(result.ast.js.start, result.ast.js.end, 0); - } else { - code.remove(result.ast.js.start, result.ast.js.end); + + if (lifecycle_functions.size > 0) { + const specifiers = Array.from(lifecycle_functions).sort().join(', '); + imports.unshift(`import { ${specifiers} } from 'svelte';`); + } + + if (imports.length) { + script_contents = `\n${imports.map(x => indent + x).join(`\n`)}\n${script_contents}`; + } } + + code.remove(result.ast.js.start, result.ast.js.end); } walk(result.ast.html, { @@ -173,73 +146,9 @@ export function upgradeTemplate(source) { upgraded = `\n\n${upgraded}`; } - return upgraded; -} - -function handleComponents(node, declarations, blocks, imports) { - const statements = []; - - node.properties.forEach(component => { - if (component.value.type === 'Literal') { - statements.push(`import ${component.key.name} from '${component.value.value}';`); - } else { - if (component.value.name !== component.key.name) { - if (declarations.has(component.key.name)) { - error(`component name conflicts with existing declaration`, component.start); - } - - statements.push(`const ${component.key.name} = ${component.value.name};`); - } - } - }); - - if (statements.length > 0) { - blocks.push(statements.join('\n')); - } -} - -function handleData(node, props, code, blocks) { - if (!/FunctionExpression/.test(node.type)) { - error(`can only convert 'data' if it is a function expression or arrow function expression`, node.start); - } - - let returned; - - if (node.body.type === 'BlockStatement') { - walk(node.body, { - enter(child, parent) { - if (child.type === 'ReturnStatement') { - if (parent !== node.body) { - error(`can only convert data with a top-level return statement`, child.start); - } - - if (returned) { - error(`duplicate return statement`, child.start); - } - - const index = node.body.body.indexOf(child); - if (index !== 0) { - throw new Error(`TODO handle statements before return`); - } - - returned = child.argument; - } - } - }); - - if (!returned) { - error(`missing return statement`, child.start); - } - } else { - returned = node.body; - while (returned.type === 'ParenthesizedExpression') returned = returned.expression; - - if (returned.type !== 'ObjectExpression') { - error(`can only convert an object literal`, returned.start); - } + if (script_contents) { + upgraded = `\n\n${upgraded}`; } - returned.properties.forEach(prop => { - props.set(prop.key.name, code.original.slice(prop.value.start, prop.value.end)); - }); + return upgraded; } \ No newline at end of file diff --git a/src/v3/findDeclarations.js b/src/v3/utils.js similarity index 73% rename from src/v3/findDeclarations.js rename to src/v3/utils.js index 7adb0ad..25078ef 100644 --- a/src/v3/findDeclarations.js +++ b/src/v3/utils.js @@ -1,6 +1,6 @@ import { walk } from 'estree-walker'; -export function findDeclarations(body) { +export function find_declarations(body) { const declarations = new Set(); walk(body, { @@ -26,4 +26,13 @@ export function findDeclarations(body) { }); return declarations; +} + +export function error(message, pos) { + const e = new Error(message); + e.pos = pos; + + // TODO add code frame + + throw e; } \ No newline at end of file diff --git a/test/v3/samples/oncreate/input.html b/test/v3/samples/oncreate/input.html new file mode 100644 index 0000000..c16dabe --- /dev/null +++ b/test/v3/samples/oncreate/input.html @@ -0,0 +1,9 @@ +

component with oncreate

+ + \ No newline at end of file diff --git a/test/v3/samples/oncreate/output.html b/test/v3/samples/oncreate/output.html new file mode 100644 index 0000000..2106c0d --- /dev/null +++ b/test/v3/samples/oncreate/output.html @@ -0,0 +1,9 @@ + + +

component with oncreate

\ No newline at end of file From c939ccecbd4f413a3594a00ff72a60c74de63757 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 5 Nov 2018 08:14:42 -0500 Subject: [PATCH 09/52] simplify --- src/v3/index.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/v3/index.js b/src/v3/index.js index 0acbe47..67caf3b 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -23,7 +23,7 @@ export function upgradeTemplate(source) { let tag; let namespace; - let script_contents; + let script_sections = []; if (result.ast.js) { const { body } = result.ast.js.content; @@ -77,7 +77,7 @@ export function upgradeTemplate(source) { props.push(`export let ${key} = ${value};`) } - if (props.length > 0) blocks.push(props.join('\n')); + if (props.length > 0) blocks.push(props.join(indent + '\n')); code.overwrite(default_export.start, default_export.end, blocks.join('\n\n')); } @@ -108,7 +108,10 @@ export function upgradeTemplate(source) { code.remove(a, b); } - script_contents = code.slice(result.ast.js.content.start, result.ast.js.content.end); + const { start } = body[0]; + const { end } = body[body.length - 1]; + + script_sections.push(code.slice(start, end)); code.move(result.ast.js.start, result.ast.js.end, 0); @@ -118,7 +121,7 @@ export function upgradeTemplate(source) { } if (imports.length) { - script_contents = `\n${imports.map(x => indent + x).join(`\n`)}\n${script_contents}`; + script_sections.unshift(`${imports.join(indent + `\n`)}`); } } @@ -138,6 +141,10 @@ export function upgradeTemplate(source) { let upgraded = code.toString().trim(); + if (script_sections.length > 0) { + upgraded = `\n\n${upgraded}`; + } + if (tag || namespace) { // TODO or bindings const attributes = []; if (tag) attributes.push(`tag="${tag}"`); @@ -146,9 +153,5 @@ export function upgradeTemplate(source) { upgraded = `\n\n${upgraded}`; } - if (script_contents) { - upgraded = `\n\n${upgraded}`; - } - return upgraded; } \ No newline at end of file From bb4bb47bcf20d11f092d0b519906970e09137431 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 5 Nov 2018 08:41:59 -0500 Subject: [PATCH 10/52] refactor a bit --- src/v3/handlers/components.js | 3 +- src/v3/handlers/data.js | 4 +- src/v3/handlers/oncreate.js | 4 +- src/v3/index.js | 59 +++++++++++++++++----------- test/v3/samples/oncreate/input.html | 2 +- test/v3/samples/oncreate/output.html | 5 ++- 6 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/v3/handlers/components.js b/src/v3/handlers/components.js index c889ba0..8e6d35a 100644 --- a/src/v3/handlers/components.js +++ b/src/v3/handlers/components.js @@ -1,4 +1,5 @@ -export default function handleComponents(node, declarations, blocks, imports) { +export default function handleComponents(node, info) { + const { declarations, blocks } = info; const statements = []; node.properties.forEach(component => { diff --git a/src/v3/handlers/data.js b/src/v3/handlers/data.js index 79d29ad..b75f76f 100644 --- a/src/v3/handlers/data.js +++ b/src/v3/handlers/data.js @@ -1,6 +1,8 @@ import { walk } from 'estree-walker'; -export default function handle_data(node, props, code, blocks) { +export default function handle_data(node, info) { + const { props, code } = info; + if (!/FunctionExpression/.test(node.type)) { error(`can only convert 'data' if it is a function expression or arrow function expression`, node.start); } diff --git a/src/v3/handlers/oncreate.js b/src/v3/handlers/oncreate.js index 9f1645a..485fa90 100644 --- a/src/v3/handlers/oncreate.js +++ b/src/v3/handlers/oncreate.js @@ -1,6 +1,6 @@ -import { error } from '../utils.js'; +export default function handle_oncreate(node, info) { + const { code, blocks, indent_regex } = info; -export default function handle_oncreate(node, code, blocks, indent_regex) { if (node.type === 'FunctionExpression') { const body = code.slice(node.body.start, node.body.end).replace(indent_regex, ''); blocks.push(`onmount(() => ${body});`); diff --git a/src/v3/index.js b/src/v3/index.js index 67caf3b..0a24d2a 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -28,35 +28,48 @@ export function upgradeTemplate(source) { if (result.ast.js) { const { body } = result.ast.js.content; - const default_values = new Map(); + const props = new Map(); result.stats.props.forEach(prop => { - default_values.set(prop, undefined); + props.set(prop, 'undefined'); }); - const lifecycle_functions = new Set(); + const info = { + code, + lifecycle_functions: new Set(), + props, + blocks: [], + imports: [], + methods: new Set(), + declarations: find_declarations(body), + indent_regex: new RegExp(`^${indent}`, 'gm') + }; - const blocks = []; - const imports = []; const default_export = body.find(node => node.type === 'ExportDefaultDeclaration'); - const declarations = find_declarations(body); - if (default_export) { // TODO set up indentExclusionRanges + default_export.declaration.properties.forEach(prop => { + if (prop.key.name === 'methods') { + prop.value.properties.forEach(method => { + info.methods.add(method.key.name); + }); + } + }); + default_export.declaration.properties.forEach(prop => { switch (prop.key.name) { case 'components': - handle_components(prop.value, declarations, blocks, imports); + handle_components(prop.value, info); break; case 'data': - handle_data(prop.value, default_values, code, blocks); + handle_data(prop.value, info); break; case 'oncreate': case 'onrender': - lifecycle_functions.add('onmount'); - handle_oncreate(prop.value, code, blocks, indent_regex); + info.lifecycle_functions.add('onmount'); + handle_oncreate(prop.value, info); case 'tag': tag = prop.value.value; @@ -71,26 +84,26 @@ export function upgradeTemplate(source) { } }); - let props = []; - for (const [key, value] of default_values) { + let prop_declarations = []; + for (const [key, value] of props) { if (key === value) continue; - props.push(`export let ${key} = ${value};`) + prop_declarations.push(`export let ${key}${value === 'undefined' ? '' : ` = ${value}`};`); } - if (props.length > 0) blocks.push(props.join(indent + '\n')); + if (prop_declarations.length > 0) info.blocks.push(prop_declarations.join(`\n${indent}`)); - code.overwrite(default_export.start, default_export.end, blocks.join('\n\n')); + code.overwrite(default_export.start, default_export.end, info.blocks.join('\n\n')); } code.appendLeft(result.ast.js.end, '\n\n'); const needs_script = ( - blocks.length > 0 || + info.blocks.length > 0 || !!body.find(node => node !== default_export) ); if (needs_script) { - if (blocks.length === 0 && default_export) { + if (info.blocks.length === 0 && default_export) { const index = body.indexOf(default_export); let a = default_export.start; @@ -115,13 +128,13 @@ export function upgradeTemplate(source) { code.move(result.ast.js.start, result.ast.js.end, 0); - if (lifecycle_functions.size > 0) { - const specifiers = Array.from(lifecycle_functions).sort().join(', '); - imports.unshift(`import { ${specifiers} } from 'svelte';`); + if (info.lifecycle_functions.size > 0) { + const specifiers = Array.from(info.lifecycle_functions).sort().join(', '); + info.imports.unshift(`import { ${specifiers} } from 'svelte';`); } - if (imports.length) { - script_sections.unshift(`${imports.join(indent + `\n`)}`); + if (info.imports.length) { + script_sections.unshift(`${info.imports.join(`\n${indent}`)}`); } } diff --git a/test/v3/samples/oncreate/input.html b/test/v3/samples/oncreate/input.html index c16dabe..d99e2dd 100644 --- a/test/v3/samples/oncreate/input.html +++ b/test/v3/samples/oncreate/input.html @@ -3,7 +3,7 @@

component with oncreate

\ No newline at end of file diff --git a/test/v3/samples/oncreate/output.html b/test/v3/samples/oncreate/output.html index 2106c0d..d7f8905 100644 --- a/test/v3/samples/oncreate/output.html +++ b/test/v3/samples/oncreate/output.html @@ -1,8 +1,11 @@ From 28de340431bf93f2a501732127333dfaeb0b6a4e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 5 Nov 2018 20:22:37 -0500 Subject: [PATCH 11/52] implement some more stuff --- src/v3/handlers/oncreate.js | 50 ++++++++++++++++++++-- src/v3/index.js | 20 ++++++--- test/v3/samples/oncreate-async/input.html | 9 ++++ test/v3/samples/oncreate-async/output.html | 13 ++++++ test/v3/samples/oncreate/output.html | 3 +- 5 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 test/v3/samples/oncreate-async/input.html create mode 100644 test/v3/samples/oncreate-async/output.html diff --git a/src/v3/handlers/oncreate.js b/src/v3/handlers/oncreate.js index 485fa90..08517b4 100644 --- a/src/v3/handlers/oncreate.js +++ b/src/v3/handlers/oncreate.js @@ -1,12 +1,54 @@ -export default function handle_oncreate(node, info) { - const { code, blocks, indent_regex } = info; +import { walk } from "estree-walker"; + +export default function handle_oncreate_ondestroy(node, info, name) { + const { code, blocks, lifecycle_functions, indent_regex } = info; + + lifecycle_functions.add(name); if (node.type === 'FunctionExpression') { + walk(node.body, { + enter(child) { + if (/^Function/.test(child.type)) { + this.skip(); + } + + if (child.type === 'MemberExpression' && child.object.type === 'ThisExpression') { + if (!child.property.computed) { + if (info.methods.has(child.property.name)) { + code.remove(child.object.start, child.property.start); + this.skip(); + } + + else { + switch (child.property.name) { + case 'fire': + info.uses_dispatch = true; + code.overwrite(child.start, child.end, `dispatch`); + break; + + case 'get': + // TODO optimise get + + case 'set': + // TODO optimise set + + default: + code.overwrite(child.object.start, child.object.end, '__this'); + } + + info.uses_this = true; + info.uses_this_properties.add(child.property.name); + } + } + } + } + }); + const body = code.slice(node.body.start, node.body.end).replace(indent_regex, ''); - blocks.push(`onmount(() => ${body});`); + blocks.push(`${name}(${node.async ? `async ` : ``}() => ${body});`); } else { - throw new Error(`TODO non-function-expression oncreate`); + throw new Error(`TODO non-function-expression ${name}`); } } \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 0a24d2a..4ce8faa 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -19,7 +19,6 @@ export function upgradeTemplate(source) { }); const indent = code.getIndentString(); - const indent_regex = new RegExp(`^${indent}`, 'gm'); let tag; let namespace; @@ -41,7 +40,10 @@ export function upgradeTemplate(source) { imports: [], methods: new Set(), declarations: find_declarations(body), - indent_regex: new RegExp(`^${indent}`, 'gm') + indent_regex: new RegExp(`^${indent}`, 'gm'), + uses_this: false, + uses_dispatch: false, + uses_this_properties: new Set() }; const default_export = body.find(node => node.type === 'ExportDefaultDeclaration'); @@ -68,8 +70,12 @@ export function upgradeTemplate(source) { break; case 'oncreate': case 'onrender': - info.lifecycle_functions.add('onmount'); - handle_oncreate(prop.value, info); + handle_oncreate(prop.value, info, 'onmount'); + break; + + case 'ondestroy': case 'onteardown': + handle_oncreate(prop.value, info, 'ondestroy'); + break; case 'tag': tag = prop.value.value; @@ -92,7 +98,7 @@ export function upgradeTemplate(source) { if (prop_declarations.length > 0) info.blocks.push(prop_declarations.join(`\n${indent}`)); - code.overwrite(default_export.start, default_export.end, info.blocks.join('\n\n')); + code.overwrite(default_export.start, default_export.end, info.blocks.join(`\n\n${indent}`)); } code.appendLeft(result.ast.js.end, '\n\n'); @@ -133,6 +139,10 @@ export function upgradeTemplate(source) { info.imports.unshift(`import { ${specifiers} } from 'svelte';`); } + if (info.uses_this) { + script_sections.unshift(`// [svelte-upgrade] suggestion:\n${indent}// manually refactor all references to __this\n${indent}const __this = {};`); + } + if (info.imports.length) { script_sections.unshift(`${info.imports.join(`\n${indent}`)}`); } diff --git a/test/v3/samples/oncreate-async/input.html b/test/v3/samples/oncreate-async/input.html new file mode 100644 index 0000000..358044b --- /dev/null +++ b/test/v3/samples/oncreate-async/input.html @@ -0,0 +1,9 @@ +

component with oncreate

+ + \ No newline at end of file diff --git a/test/v3/samples/oncreate-async/output.html b/test/v3/samples/oncreate-async/output.html new file mode 100644 index 0000000..4651358 --- /dev/null +++ b/test/v3/samples/oncreate-async/output.html @@ -0,0 +1,13 @@ + + +

component with oncreate

\ No newline at end of file diff --git a/test/v3/samples/oncreate/output.html b/test/v3/samples/oncreate/output.html index d7f8905..6b46041 100644 --- a/test/v3/samples/oncreate/output.html +++ b/test/v3/samples/oncreate/output.html @@ -1,7 +1,8 @@ \ No newline at end of file diff --git a/test/v3/samples/method/output.html b/test/v3/samples/method/output.html new file mode 100644 index 0000000..67511ea --- /dev/null +++ b/test/v3/samples/method/output.html @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/oncreate-async/output.html b/test/v3/samples/oncreate-async/output.html index 4651358..2de424c 100644 --- a/test/v3/samples/oncreate-async/output.html +++ b/test/v3/samples/oncreate-async/output.html @@ -1,7 +1,7 @@ \n\n${upgraded}`; } - if (tag || namespace) { // TODO or bindings + if (tag || namespace || immutable) { // TODO or bindings const attributes = []; if (tag) attributes.push(`tag="${tag}"`); if (namespace) attributes.push(`namespace="${namespace}"`); + if (immutable) attributes.push(`immutable`); upgraded = `\n\n${upgraded}`; } diff --git a/test/v3/samples/action-identifier/input.html b/test/v3/samples/action-identifier/input.html new file mode 100644 index 0000000..c93aea8 --- /dev/null +++ b/test/v3/samples/action-identifier/input.html @@ -0,0 +1,11 @@ +
+ + \ No newline at end of file diff --git a/test/v3/samples/action-identifier/output.html b/test/v3/samples/action-identifier/output.html new file mode 100644 index 0000000..c1c7634 --- /dev/null +++ b/test/v3/samples/action-identifier/output.html @@ -0,0 +1,5 @@ + + +
\ No newline at end of file diff --git a/test/v3/samples/action-inline/input.html b/test/v3/samples/action-inline/input.html new file mode 100644 index 0000000..f0df34a --- /dev/null +++ b/test/v3/samples/action-inline/input.html @@ -0,0 +1,11 @@ +
+ + \ No newline at end of file diff --git a/test/v3/samples/action-inline/output.html b/test/v3/samples/action-inline/output.html new file mode 100644 index 0000000..df6aeed --- /dev/null +++ b/test/v3/samples/action-inline/output.html @@ -0,0 +1,7 @@ + + +
\ No newline at end of file diff --git a/test/v3/samples/immutable/input.html b/test/v3/samples/immutable/input.html new file mode 100644 index 0000000..ddf8bec --- /dev/null +++ b/test/v3/samples/immutable/input.html @@ -0,0 +1,7 @@ +

Immutable foo: {foo}

+ + \ No newline at end of file diff --git a/test/v3/samples/immutable/output.html b/test/v3/samples/immutable/output.html new file mode 100644 index 0000000..23220a9 --- /dev/null +++ b/test/v3/samples/immutable/output.html @@ -0,0 +1,7 @@ + + + + +

Immutable foo: {foo}

\ No newline at end of file From ba58d90834530e677b718c34ae7ec448263b987b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 6 Nov 2018 10:26:50 -0500 Subject: [PATCH 16/52] setup --- src/v3/handlers/actions.js | 5 ----- src/v3/handlers/setup.js | 8 ++++++++ src/v3/index.js | 27 +++++++++++++++++++++++++-- test/v3/samples/setup/input.html | 11 +++++++++++ test/v3/samples/setup/output.html | 14 ++++++++++++++ 5 files changed, 58 insertions(+), 7 deletions(-) delete mode 100644 src/v3/handlers/actions.js create mode 100644 src/v3/handlers/setup.js create mode 100644 test/v3/samples/setup/input.html create mode 100644 test/v3/samples/setup/output.html diff --git a/src/v3/handlers/actions.js b/src/v3/handlers/actions.js deleted file mode 100644 index e6335ff..0000000 --- a/src/v3/handlers/actions.js +++ /dev/null @@ -1,5 +0,0 @@ -import handle_registrants from './shared/handle_registrants.js'; - -export default function handle_actions(node, info) { - handle_registrants(node.properties, info, 'action'); -} \ No newline at end of file diff --git a/src/v3/handlers/setup.js b/src/v3/handlers/setup.js new file mode 100644 index 0000000..4e8fff7 --- /dev/null +++ b/src/v3/handlers/setup.js @@ -0,0 +1,8 @@ +export default function handle_methods(node, info) { + const { shared_blocks, code, indent } = info; + + const setup = code.slice(node.start, node.end) + .replace(info.indent_regex, ''); + + shared_blocks.push(`/*\n${indent}svelte-upgrade cannot automatically transform this code —\n${indent}it must be updated manually\n\n${indent}${setup}\n${indent}*/`); +} \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 29a0f46..ef0da74 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -1,13 +1,14 @@ import * as svelte from 'svelte'; import MagicString from 'magic-string'; import { walk, childKeys } from 'estree-walker'; -import handle_actions from './handlers/actions.js'; import handle_components from './handlers/components.js'; import handle_data from './handlers/data.js'; import handle_methods from './handlers/methods.js'; import handle_oncreate from './handlers/oncreate.js'; import handle_on_directive from './handlers/on_directive'; import handle_use_directive from './handlers/use_directive'; +import handle_registrants from './handlers/shared/handle_registrants.js'; +import handle_setup from './handlers/setup.js'; import { error, find_declarations } from './utils.js'; // We need to tell estree-walker that it should always @@ -39,6 +40,7 @@ export function upgradeTemplate(source) { lifecycle_functions: new Set(), props, blocks: [], + shared_blocks: [], imports: [], methods: new Set(), declarations: new Set(), @@ -69,7 +71,11 @@ export function upgradeTemplate(source) { default_export.declaration.properties.forEach(prop => { switch (prop.key.name) { case 'actions': - handle_actions(prop.value, info); + handle_registrants(prop.value.properties, info, 'action') + break; + + case 'animations': + handle_registrants(prop.value.properties, info, 'animation') break; case 'components': @@ -80,6 +86,10 @@ export function upgradeTemplate(source) { handle_data(prop.value, info); break; + case 'events': + handle_registrants(prop.value.properties, info, 'event') + break; + case 'immutable': immutable = prop.value.value; break; @@ -96,10 +106,18 @@ export function upgradeTemplate(source) { handle_oncreate(prop.value, info, 'ondestroy'); break; + case 'setup': + handle_setup(prop, info); + break; + case 'tag': tag = prop.value.value; break; + case 'transitions': + handle_registrants(prop.value.properties, info, 'transition') + break; + case 'namespace': namespace = prop.value.value; break; @@ -192,6 +210,11 @@ export function upgradeTemplate(source) { upgraded = `\n\n${upgraded}`; } + if (info.shared_blocks.length > 0) { + // scope="shared" is subject to change + upgraded = `\n\n${upgraded}`; + } + if (tag || namespace || immutable) { // TODO or bindings const attributes = []; if (tag) attributes.push(`tag="${tag}"`); diff --git a/test/v3/samples/setup/input.html b/test/v3/samples/setup/input.html new file mode 100644 index 0000000..4f75a4e --- /dev/null +++ b/test/v3/samples/setup/input.html @@ -0,0 +1,11 @@ +

text

+ + \ No newline at end of file diff --git a/test/v3/samples/setup/output.html b/test/v3/samples/setup/output.html new file mode 100644 index 0000000..06ba65e --- /dev/null +++ b/test/v3/samples/setup/output.html @@ -0,0 +1,14 @@ + + +

text

\ No newline at end of file From 8145c1c5aec6f37a864c31f809f37f713e84f2b3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 6 Nov 2018 10:40:25 -0500 Subject: [PATCH 17/52] preload --- src/v3/handlers/preload.js | 18 ++++++++++++++++++ src/v3/index.js | 5 +++++ test/v3/samples/preload/input.html | 10 ++++++++++ test/v3/samples/preload/output.html | 12 ++++++++++++ 4 files changed, 45 insertions(+) create mode 100644 src/v3/handlers/preload.js create mode 100644 test/v3/samples/preload/input.html create mode 100644 test/v3/samples/preload/output.html diff --git a/src/v3/handlers/preload.js b/src/v3/handlers/preload.js new file mode 100644 index 0000000..75d7ffc --- /dev/null +++ b/src/v3/handlers/preload.js @@ -0,0 +1,18 @@ +export default function handle_preload(preload, info) { + const { shared_blocks, code } = info; + + if (preload.type === 'Identifier') { + shared_blocks.push(preload.name === `preload`) + ? `export { preload };` + : `export { ${preload.name} as preload };` + } else if (preload.type === 'FunctionExpression') { + const body = code.slice(preload.body.start, preload.body.end) + .replace(info.indent_regex, ''); + + const args = preload.params.length > 0 + ? `(${code.slice(preload.params[0].start, preload.params[preload.params.length - 1].end)})` + : '()'; + + shared_blocks.push(`export ${preload.async ? `async ` : ``}function preload${args} ${body}`) + } +} \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index ef0da74..addab16 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -8,6 +8,7 @@ import handle_oncreate from './handlers/oncreate.js'; import handle_on_directive from './handlers/on_directive'; import handle_use_directive from './handlers/use_directive'; import handle_registrants from './handlers/shared/handle_registrants.js'; +import handle_preload from './handlers/preload.js'; import handle_setup from './handlers/setup.js'; import { error, find_declarations } from './utils.js'; @@ -106,6 +107,10 @@ export function upgradeTemplate(source) { handle_oncreate(prop.value, info, 'ondestroy'); break; + case 'preload': + handle_preload(prop.value, info); + break; + case 'setup': handle_setup(prop, info); break; diff --git a/test/v3/samples/preload/input.html b/test/v3/samples/preload/input.html new file mode 100644 index 0000000..ef51554 --- /dev/null +++ b/test/v3/samples/preload/input.html @@ -0,0 +1,10 @@ +

Hello {user.name}

+ + \ No newline at end of file diff --git a/test/v3/samples/preload/output.html b/test/v3/samples/preload/output.html new file mode 100644 index 0000000..5c56d80 --- /dev/null +++ b/test/v3/samples/preload/output.html @@ -0,0 +1,12 @@ + + + + +

Hello {user.name}

\ No newline at end of file From 67803527a00f62a5e6c2524b19f9785387163392 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 6 Nov 2018 11:41:01 -0500 Subject: [PATCH 18/52] WIP computed properties --- package.json | 1 + src/v3/handlers/computed.js | 65 ++++++++++ src/v3/index.js | 14 ++- src/v3/scopes.js | 113 ++++++++++++++++++ .../computed-nested-destructuring/input.html | 15 +++ .../computed-nested-destructuring/output.html | 15 +++ test/v3/samples/computed/input.html | 16 +++ test/v3/samples/computed/output.html | 20 ++++ yarn.lock | 12 ++ 9 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/v3/handlers/computed.js create mode 100644 src/v3/scopes.js create mode 100644 test/v3/samples/computed-nested-destructuring/input.html create mode 100644 test/v3/samples/computed-nested-destructuring/output.html create mode 100644 test/v3/samples/computed/input.html create mode 100644 test/v3/samples/computed/output.html diff --git a/package.json b/package.json index 45edfb9..e762f3b 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dependencies": { "estree-walker": "^0.5.2", "glob": "^7.1.3", + "is-reference": "^1.1.0", "kleur": "^2.0.2", "magic-string": "^0.25.1", "prompts": "^1.2.0", diff --git a/src/v3/handlers/computed.js b/src/v3/handlers/computed.js new file mode 100644 index 0000000..58726d3 --- /dev/null +++ b/src/v3/handlers/computed.js @@ -0,0 +1,65 @@ +import { walk } from 'estree-walker'; +import { create_scopes } from '../scopes'; +import is_reference from 'is-reference'; + +export default function handle_computed(node, info) { + const { props, code, blocks, indent } = info; + + node.properties.forEach(computed => { + const declarations = computed.value.params[0].properties + .filter(param => param.value.type !== 'Identifier') + .map(param => { + const { name } = param.key; + const lhs = code.slice(param.value.start, param.value.end); + const rhs = info.computed.has(name) ? `${name}()` : name; + return `const ${lhs} = ${rhs};` + }); + + // need to rewrite x => x() if x is computed + let { map, scope } = create_scopes(computed.value.body); + walk(computed.value.body, { + enter(node, parent) { + if (map.has(node)) { + scope = map.get(node); + } + + if (is_reference(node, parent)) { + if (info.computed.has(node.name)) { + code.appendLeft(node.end, '()'); + } + } + }, + + leave(node) { + if (map.has(node)) { + scope = scope.parent; + } + } + }); + + const implicit_return = ( + computed.value.type === 'ArrowFunctionExpression' && + computed.value.body.type !== 'BlockStatement' + ); + + if (implicit_return) { + const expression = code.slice(computed.value.body.start, computed.value.body.end); + const statements = declarations.concat(`return ${expression};`).join(`\n${indent}${indent}`); + + blocks.push(`function ${computed.key.name}() {\n${indent}${indent}${statements}\n${indent}}`); + } else { + if (declarations.length) { + const i = indent + indent + indent + indent; + + const declaration_block = declarations.join(`\n${i}`); + code.appendLeft(computed.value.body.start + 1, `\n${i}${declaration_block}`); + } + + const body = code.slice(computed.value.body.start, computed.value.body.end) + .replace(info.indent_regex, '') + .replace(info.indent_regex, ''); + + blocks.push(`function ${computed.key.name}() ${body}`); + } + }); +} \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index addab16..49eb9ed 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -2,6 +2,7 @@ import * as svelte from 'svelte'; import MagicString from 'magic-string'; import { walk, childKeys } from 'estree-walker'; import handle_components from './handlers/components.js'; +import handle_computed from './handlers/computed.js'; import handle_data from './handlers/data.js'; import handle_methods from './handlers/methods.js'; import handle_oncreate from './handlers/oncreate.js'; @@ -44,6 +45,7 @@ export function upgradeTemplate(source) { shared_blocks: [], imports: [], methods: new Set(), + computed: new Set(), declarations: new Set(), indent, indent_regex: new RegExp(`^${indent}`, 'gm'), @@ -67,6 +69,12 @@ export function upgradeTemplate(source) { info.methods.add(method.key.name); }); } + + if (prop.key.name === 'computed') { + prop.value.properties.forEach(method => { + info.computed.add(method.key.name); + }); + } }); default_export.declaration.properties.forEach(prop => { @@ -83,6 +91,10 @@ export function upgradeTemplate(source) { handle_components(prop.value, info); break; + case 'computed': + handle_computed(prop.value, info); + break; + case 'data': handle_data(prop.value, info); break; @@ -138,7 +150,7 @@ export function upgradeTemplate(source) { prop_declarations.push(`export let ${key}${value === 'undefined' ? '' : ` = ${value}`};`); } - if (prop_declarations.length > 0) info.blocks.push(prop_declarations.join(`\n${indent}`)); + if (prop_declarations.length > 0) info.blocks.unshift(prop_declarations.join(`\n${indent}`)); code.overwrite(default_export.start, default_export.end, info.blocks.join(`\n\n${indent}`)); } diff --git a/src/v3/scopes.js b/src/v3/scopes.js new file mode 100644 index 0000000..f6ec542 --- /dev/null +++ b/src/v3/scopes.js @@ -0,0 +1,113 @@ +import { walk } from 'estree-walker'; +import is_reference from 'is-reference'; + +export function create_scopes(expression) { + const map = new WeakMap(); + + const globals = new Set(); + let scope = new Scope(null, false); + + walk(expression, { + enter(node, parent) { + if (/Function/.test(node.type)) { + if (node.type === 'FunctionDeclaration') { + scope.declarations.add(node.id.name); + } else { + scope = new Scope(scope, false); + map.set(node, scope); + if (node.id) scope.declarations.add(node.id.name); + } + + node.params.forEach((param) => { + extract_names(param).forEach(name => { + scope.declarations.add(name); + }); + }); + } else if (/For(?:In|Of)Statement/.test(node.type)) { + scope = new Scope(scope, true); + map.set(node, scope); + } else if (node.type === 'BlockStatement') { + scope = new Scope(scope, true); + map.set(node, scope); + } else if (/(Function|Class|Variable)Declaration/.test(node.type)) { + scope.addDeclaration(node); + } else if (is_reference(node, parent)) { + if (!scope.has(node.name)) { + globals.add(node.name); + } + } + }, + + leave(node) { + if (map.has(node)) { + scope = scope.parent; + } + }, + }); + + return { map, scope, globals }; +} + +export class Scope { + constructor(parent, block) { + this.parent = parent; + this.block = block; + this.declarations = new Set(); + } + + addDeclaration(node) { + if (node.kind === 'var' && !this.block && this.parent) { + this.parent.addDeclaration(node); + } else if (node.type === 'VariableDeclaration') { + node.declarations.forEach((declarator) => { + extract_names(declarator.id).forEach(name => { + this.declarations.add(name); + }); + }); + } else { + this.declarations.add(node.id.name); + } + } + + has(name) { + return ( + this.declarations.has(name) || (this.parent && this.parent.has(name)) + ); + } +} + +function extract_names(param) { + const names = []; + extractors[param.type](names, param); + return names; +} + +const extractors = { + Identifier(names, param) { + names.push(param.name); + }, + + ObjectPattern(names, param) { + param.properties.forEach((prop) => { + if (prop.type === 'RestElement') { + names.push(prop.argument.name); + } else { + extractors[prop.value.type](names, prop.value); + } + }); + }, + + ArrayPattern(names, param) { + param.elements.forEach((element) => { + if (element) extractors[element.type](names, element); + }); + }, + + RestElement(names, param) { + extractors[param.argument.type](names, param.argument); + }, + + AssignmentPattern(names, param) { + extractors[param.left.type](names, param.left); + }, +}; diff --git a/test/v3/samples/computed-nested-destructuring/input.html b/test/v3/samples/computed-nested-destructuring/input.html new file mode 100644 index 0000000..43f5183 --- /dev/null +++ b/test/v3/samples/computed-nested-destructuring/input.html @@ -0,0 +1,15 @@ +

len: {len}

+ + \ No newline at end of file diff --git a/test/v3/samples/computed-nested-destructuring/output.html b/test/v3/samples/computed-nested-destructuring/output.html new file mode 100644 index 0000000..c081afe --- /dev/null +++ b/test/v3/samples/computed-nested-destructuring/output.html @@ -0,0 +1,15 @@ + + +

len: {len()}

\ No newline at end of file diff --git a/test/v3/samples/computed/input.html b/test/v3/samples/computed/input.html new file mode 100644 index 0000000..af5c49b --- /dev/null +++ b/test/v3/samples/computed/input.html @@ -0,0 +1,16 @@ +

{a} + {b} = {c}

+

{c} * {c} = {d}

+

{d} ^ {d} = {e}

+ + \ No newline at end of file diff --git a/test/v3/samples/computed/output.html b/test/v3/samples/computed/output.html new file mode 100644 index 0000000..bd8ea21 --- /dev/null +++ b/test/v3/samples/computed/output.html @@ -0,0 +1,20 @@ + + +

{a} + {b} = {c()}

+

{c()} * {c()} = {d()}

+

{d()} ^ {d()} = {e()}

\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 1dbe103..d85329b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@types/estree@0.0.38": + version "0.0.38" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2" + integrity sha512-F/v7t1LwS4vnXuPooJQGBRKRGIoxWUTmA4VHfqjOccFsNDThD5bfUNpITive6s352O7o384wcpEaDV8rHCehDA== + "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" @@ -216,6 +221,13 @@ is-primitive@^2.0.0: resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= +is-reference@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.0.tgz#50e6ef3f64c361e2c53c0416cdc9420037f2685b" + integrity sha512-h37O/IX4efe56o9k41II1ECMqKwtqHa7/12dLDEzJIFux2x15an4WCDb0/eKdmUgRpLJ3bR0DrzDc7vOrVgRDw== + dependencies: + "@types/estree" "0.0.38" + isarray@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" From c8bda5a1ca00a808ace642bbd68167ce387ffa0f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 6 Nov 2018 11:53:56 -0500 Subject: [PATCH 19/52] rewrite computed props in templates --- src/v3/index.js | 42 ++++++++++++++++++++++++++++++++++++++ src/v3/rewrite_computed.js | 27 ++++++++++++++++++++++++ src/v3/scopes.js | 2 +- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/v3/rewrite_computed.js diff --git a/src/v3/index.js b/src/v3/index.js index 49eb9ed..72db273 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -12,6 +12,8 @@ import handle_registrants from './handlers/shared/handle_registrants.js'; import handle_preload from './handlers/preload.js'; import handle_setup from './handlers/setup.js'; import { error, find_declarations } from './utils.js'; +import { extract_names } from './scopes.js'; +import rewrite_computed from './rewrite_computed.js'; // We need to tell estree-walker that it should always // look for an `else` block, otherwise it might get @@ -207,9 +209,36 @@ export function upgradeTemplate(source) { code.remove(result.ast.js.start, result.ast.js.end); } + let scope = new Set(); + const scopes = [scope]; + walk(result.ast.html, { enter(node, parent) { switch (node.type) { + case 'EachBlock': + scope = new Set(scope); + extract_names(node.context).forEach(name => { + scope.add(name); + }); + scopes.push(scope); + break; + + case 'ThenBlock': + if (parent.value) { + scope = new Set(scope); + scope.add(parent.value); + scopes.push(scope); + } + break; + + case 'CatchBlock': + if (parent.error) { + scope = new Set(scope); + scope.add(parent.error); + scopes.push(scope); + } + break; + case 'EventHandler': handle_on_directive(node, info, parent); break; @@ -217,6 +246,19 @@ export function upgradeTemplate(source) { case 'Action': handle_use_directive(node, info, parent); break; + + case 'MustacheTag': + case 'RawMustacheTag': + // TODO also need to do this for expressions in blocks, attributes and directives + rewrite_computed(node, info, scope); + break; + } + }, + + leave(node) { + if (node.type === 'EachBlock' || node.type === 'ThenBlock' || node.type === 'CatchBlock') { + scopes.pop(); + scope = scopes[scopes.length - 1]; } } }); diff --git a/src/v3/rewrite_computed.js b/src/v3/rewrite_computed.js new file mode 100644 index 0000000..5cb0884 --- /dev/null +++ b/src/v3/rewrite_computed.js @@ -0,0 +1,27 @@ +import { walk } from 'estree-walker'; +import is_reference from 'is-reference'; +import { create_scopes } from './scopes.js'; + +export default function rewrite_computed(node, info, template_scope) { + let { map, scope } = create_scopes(node); + + walk(node, { + enter(node, parent) { + if (map.has(node)) { + scope = map.get(node); + } + + if (is_reference(node, parent)) { + if (!scope.has(node.name) && info.computed.has(node.name)) { + info.code.appendLeft(node.end, '()'); + } + } + }, + + leave(node) { + if (map.has(node)) { + scope = scope.parent; + } + } + }); +} \ No newline at end of file diff --git a/src/v3/scopes.js b/src/v3/scopes.js index f6ec542..ab2a918 100644 --- a/src/v3/scopes.js +++ b/src/v3/scopes.js @@ -76,7 +76,7 @@ export class Scope { } } -function extract_names(param) { +export function extract_names(param) { const names = []; extractors[param.type](names, param); return names; From d3e06e37f7332a5a0bd89a83b4f66cc683b8d4b9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 6 Nov 2018 13:12:02 -0500 Subject: [PATCH 20/52] globals, non-identifier registrants --- src/v3/handlers/components.js | 2 +- src/v3/handlers/shared/alias_registration.js | 13 ++++--- src/v3/index.js | 34 +++++++++++++++++-- .../samples/component-non-imported/input.html | 9 +++++ .../component-non-imported/output.html | 5 +++ test/v3/samples/globals/input.html | 12 +++++++ test/v3/samples/globals/output.html | 6 ++++ 7 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 test/v3/samples/component-non-imported/input.html create mode 100644 test/v3/samples/component-non-imported/output.html create mode 100644 test/v3/samples/globals/input.html create mode 100644 test/v3/samples/globals/output.html diff --git a/src/v3/handlers/components.js b/src/v3/handlers/components.js index 1d35153..1f03e2d 100644 --- a/src/v3/handlers/components.js +++ b/src/v3/handlers/components.js @@ -1,4 +1,4 @@ -import alias_registration from "./shared/alias_registration"; +import alias_registration from './shared/alias_registration.js'; export default function handle_components(node, info) { const { blocks } = info; diff --git a/src/v3/handlers/shared/alias_registration.js b/src/v3/handlers/shared/alias_registration.js index d167e55..2291379 100644 --- a/src/v3/handlers/shared/alias_registration.js +++ b/src/v3/handlers/shared/alias_registration.js @@ -1,11 +1,14 @@ import { error } from '../../utils.js'; export default function alias_registration(node, info, statements, type) { - if (node.value.name !== node.key.name) { - if (info.declarations.has(node.key.name)) { - error(`${type} name conflicts with existing declaration`, node.start); - } + if (node.value.type === 'Identifier' && node.value.name === node.key.name) { + return; + } - statements.push(`const ${node.key.name} = ${node.value.name};`); + if (info.declarations.has(node.key.name)) { + error(`${type} name conflicts with existing declaration`, node.start); } + + const rhs = info.code.slice(node.value.start, node.value.end); + statements.push(`const ${node.key.name} = ${rhs};`); } \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 72db273..63bae6c 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -21,6 +21,34 @@ import rewrite_computed from './rewrite_computed.js'; childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; childKeys.Attribute = ['value']; +const global_whitelist = new Set([ + 'Array', + 'Boolean', + 'console', + 'Date', + 'decodeURI', + 'decodeURIComponent', + 'encodeURI', + 'encodeURIComponent', + 'Infinity', + 'Intl', + 'isFinite', + 'isNaN', + 'JSON', + 'Map', + 'Math', + 'NaN', + 'Number', + 'Object', + 'parseFloat', + 'parseInt', + 'Promise', + 'RegExp', + 'Set', + 'String', + 'undefined', +]); + export function upgradeTemplate(source) { const code = new MagicString(source); const result = svelte.compile(source, { @@ -36,7 +64,9 @@ export function upgradeTemplate(source) { const props = new Map(); result.stats.props.forEach(prop => { - props.set(prop, 'undefined'); + if (!global_whitelist.has(prop)) { + props.set(prop, 'undefined'); + } }); const info = { @@ -283,5 +313,5 @@ export function upgradeTemplate(source) { upgraded = `\n\n${upgraded}`; } - return upgraded; + return upgraded.trim(); } \ No newline at end of file diff --git a/test/v3/samples/component-non-imported/input.html b/test/v3/samples/component-non-imported/input.html new file mode 100644 index 0000000..731ab61 --- /dev/null +++ b/test/v3/samples/component-non-imported/input.html @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/component-non-imported/output.html b/test/v3/samples/component-non-imported/output.html new file mode 100644 index 0000000..42cc91f --- /dev/null +++ b/test/v3/samples/component-non-imported/output.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/globals/input.html b/test/v3/samples/globals/input.html new file mode 100644 index 0000000..6b18205 --- /dev/null +++ b/test/v3/samples/globals/input.html @@ -0,0 +1,12 @@ +

{Math.max(a, b)}

+ + \ No newline at end of file diff --git a/test/v3/samples/globals/output.html b/test/v3/samples/globals/output.html new file mode 100644 index 0000000..339a892 --- /dev/null +++ b/test/v3/samples/globals/output.html @@ -0,0 +1,6 @@ + + +

{Math.max(a, b)}

\ No newline at end of file From 0018b9485c98c3013803698b2708abe0318bbdf9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 6 Nov 2018 13:48:16 -0500 Subject: [PATCH 21/52] error on duplicate declarations --- package.json | 1 + src/v3/handlers/data.js | 2 +- src/v3/handlers/methods.js | 5 +- src/v3/handlers/on_directive.js | 3 +- src/v3/handlers/shared/add_declaration.js | 7 +++ src/v3/handlers/shared/alias_registration.js | 6 +-- src/v3/handlers/shared/handle_registrants.js | 5 +- src/v3/handlers/shared/rewrite_set.js | 6 +-- src/v3/index.js | 12 ++++- src/v3/scopes.js | 1 + src/v3/utils.js | 52 ++++++++++++++++--- test/test.js | 45 ++++++++++++++-- .../samples/duplicate-declarations/error.js | 11 ++++ .../samples/duplicate-declarations/input.html | 16 ++++++ yarn.lock | 5 ++ 15 files changed, 148 insertions(+), 29 deletions(-) create mode 100644 src/v3/handlers/shared/add_declaration.js create mode 100644 test/v3/samples/duplicate-declarations/error.js create mode 100644 test/v3/samples/duplicate-declarations/input.html diff --git a/package.json b/package.json index e762f3b..45c6ae4 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "glob": "^7.1.3", "is-reference": "^1.1.0", "kleur": "^2.0.2", + "locate-character": "^2.0.5", "magic-string": "^0.25.1", "prompts": "^1.2.0", "sade": "^1.4.1", diff --git a/src/v3/handlers/data.js b/src/v3/handlers/data.js index b75f76f..b08fe3a 100644 --- a/src/v3/handlers/data.js +++ b/src/v3/handlers/data.js @@ -1,7 +1,7 @@ import { walk } from 'estree-walker'; export default function handle_data(node, info) { - const { props, code } = info; + const { props, code, error } = info; if (!/FunctionExpression/.test(node.type)) { error(`can only convert 'data' if it is a function expression or arrow function expression`, node.start); diff --git a/src/v3/handlers/methods.js b/src/v3/handlers/methods.js index aad4bbe..97a70d2 100644 --- a/src/v3/handlers/methods.js +++ b/src/v3/handlers/methods.js @@ -1,8 +1,8 @@ -import { error } from '../utils.js'; import rewrite_this from './shared/rewrite_this.js'; +import add_declaration from './shared/add_declaration.js'; export default function handle_methods(node, info) { - const { blocks, code } = info; + const { blocks, code, error } = info; const statements = []; let suggested = false; @@ -27,6 +27,7 @@ export default function handle_methods(node, info) { suggested = true; + add_declaration(method.key, info); blocks.push(`${suggestion}export ${node.async ? `async ` : ``}function ${method.key.name}${args} ${str}`); } else if (method.value.type === 'Identifier') { throw new Error(`TODO identifier methods`); diff --git a/src/v3/handlers/on_directive.js b/src/v3/handlers/on_directive.js index c2314d7..8bb6e15 100644 --- a/src/v3/handlers/on_directive.js +++ b/src/v3/handlers/on_directive.js @@ -1,4 +1,3 @@ -import { error } from '../utils.js'; import rewrite_this from './shared/rewrite_this.js'; import { walk } from 'estree-walker'; @@ -7,7 +6,7 @@ const voidElementNames = /^(?:area|base|br|col|command|embed|hr|img|input|keygen export default function handle_on_directive(node, info, parent) { if (!node.expression) return; - const { blocks, code } = info; + const { code } = info; if (node.expression.arguments.length === 0) { code.remove(node.expression.callee.end, node.expression.end); diff --git a/src/v3/handlers/shared/add_declaration.js b/src/v3/handlers/shared/add_declaration.js new file mode 100644 index 0000000..79c5479 --- /dev/null +++ b/src/v3/handlers/shared/add_declaration.js @@ -0,0 +1,7 @@ +export default function add_declaration(node, info) { + if (info.declarations.has(node.name)) { + info.error(`'${node.name}' conflicts with existing declaration`, node.start); + } + + info.declarations.add(node.name); +} \ No newline at end of file diff --git a/src/v3/handlers/shared/alias_registration.js b/src/v3/handlers/shared/alias_registration.js index 2291379..6e050bc 100644 --- a/src/v3/handlers/shared/alias_registration.js +++ b/src/v3/handlers/shared/alias_registration.js @@ -1,13 +1,11 @@ -import { error } from '../../utils.js'; +import add_declaration from './add_declaration.js'; export default function alias_registration(node, info, statements, type) { if (node.value.type === 'Identifier' && node.value.name === node.key.name) { return; } - if (info.declarations.has(node.key.name)) { - error(`${type} name conflicts with existing declaration`, node.start); - } + add_declaration(node.key, info); const rhs = info.code.slice(node.value.start, node.value.end); statements.push(`const ${node.key.name} = ${rhs};`); diff --git a/src/v3/handlers/shared/handle_registrants.js b/src/v3/handlers/shared/handle_registrants.js index 60eeaab..a03374c 100644 --- a/src/v3/handlers/shared/handle_registrants.js +++ b/src/v3/handlers/shared/handle_registrants.js @@ -1,9 +1,9 @@ -import { error } from '../../utils.js'; import rewrite_this from './rewrite_this.js'; import alias_registration from './alias_registration.js'; +import add_declaration from './add_declaration.js'; export default function handle_registrants(registrants, info, type) { - const { blocks, code } = info; + const { blocks, code, error } = info; const statements = []; registrants.forEach(registrant => { @@ -20,6 +20,7 @@ export default function handle_registrants(registrants, info, type) { ? `(${code.slice(params[0].start, params[params.length - 1].end)})` : '()'; + add_declaration(registrant.key, info); blocks.push(`function ${registrant.key.name}${args} ${str}`); } else if (registrant.value.type === 'Identifier') { alias_registration(registrant, info, statements, type); diff --git a/src/v3/handlers/shared/rewrite_set.js b/src/v3/handlers/shared/rewrite_set.js index 5813f39..57db05b 100644 --- a/src/v3/handlers/shared/rewrite_set.js +++ b/src/v3/handlers/shared/rewrite_set.js @@ -1,12 +1,10 @@ -import { error } from '../../utils.js'; - export default function rewrite_set(node, info) { if (node.arguments.length !== 1) { - error(`expected a single argument`, node.start); + info.error(`expected a single argument`, node.start); } if (node.arguments[0].type !== 'ObjectExpression') { - error(`expected an object literal`, node.arguments[0].start); + info.error(`expected an object literal`, node.arguments[0].start); } const { properties } = node.arguments[0]; diff --git a/src/v3/index.js b/src/v3/index.js index 63bae6c..e9a817f 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -11,7 +11,7 @@ import handle_use_directive from './handlers/use_directive'; import handle_registrants from './handlers/shared/handle_registrants.js'; import handle_preload from './handlers/preload.js'; import handle_setup from './handlers/setup.js'; -import { error, find_declarations } from './utils.js'; +import { find_declarations, get_code_frame } from './utils.js'; import { extract_names } from './scopes.js'; import rewrite_computed from './rewrite_computed.js'; @@ -83,7 +83,15 @@ export function upgradeTemplate(source) { indent_regex: new RegExp(`^${indent}`, 'gm'), uses_this: false, uses_dispatch: false, - uses_this_properties: new Set() + uses_this_properties: new Set(), + + error(message, pos) { + const e = new Error(message); + e.pos = pos; + e.frame = get_code_frame(source, pos); + + throw e; + } }; if (result.ast.js) { diff --git a/src/v3/scopes.js b/src/v3/scopes.js index ab2a918..5f906cc 100644 --- a/src/v3/scopes.js +++ b/src/v3/scopes.js @@ -78,6 +78,7 @@ export class Scope { export function extract_names(param) { const names = []; + console.log(param.type); extractors[param.type](names, param); return names; } diff --git a/src/v3/utils.js b/src/v3/utils.js index ef2fdf3..3a7e8f6 100644 --- a/src/v3/utils.js +++ b/src/v3/utils.js @@ -1,4 +1,6 @@ import { walk } from 'estree-walker'; +import { extract_names } from './scopes'; +import { locate } from 'locate-character'; export function find_declarations(body, declarations) { walk(body, { @@ -10,15 +12,19 @@ export function find_declarations(body, declarations) { } else if (node.type === 'ClassDeclaration') { - + declarations.add(node.id.name); } else if (node.type === 'FunctionDeclaration') { - + declarations.add(node.id.name); } else if (node.type === 'VariableDeclaration') { - + node.declarations.forEach(declarator => { + extract_names(declarator.id).forEach(name => { + declarations.add(name); + }); + }); } } }); @@ -26,11 +32,41 @@ export function find_declarations(body, declarations) { return declarations; } -export function error(message, pos) { - const e = new Error(message); - e.pos = pos; +function repeat(str, i) { + let result = ''; + while (i--) result += str; + return result; +} + +function tabs_to_spaces(str) { + return str.replace(/^\t+/, match => match.split('\t').join(' ')); +} + +export function get_code_frame(source, pos) { + const { line, column } = locate(source, pos); + + const lines = source.split('\n'); + + const frameStart = Math.max(0, line - 2); + const frameEnd = Math.min(line + 3, lines.length); + + const digits = String(frameEnd + 1).length; - // TODO add code frame + return lines + .slice(frameStart, frameEnd) + .map((str, i) => { + const isErrorLine = frameStart + i === line; + + let lineNum = String(i + frameStart + 1); + while (lineNum.length < digits) lineNum = ` ${lineNum}`; + + if (isErrorLine) { + const indicator = + repeat(' ', digits + 2 + tabs_to_spaces(str.slice(0, column)).length) + '^'; + return `${lineNum}: ${tabs_to_spaces(str)}\n${indicator}`; + } - throw e; + return `${lineNum}: ${tabs_to_spaces(str)}`; + }) + .join('\n'); } \ No newline at end of file diff --git a/test/test.js b/test/test.js index bb87858..184a60d 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,5 @@ import * as fs from 'fs'; +import * as path from 'path'; import { test } from 'tape-modern'; import { v2, v3 } from '../src/index'; @@ -19,10 +20,38 @@ function testVersion(v, upgrader) { if (tests.size && !tests.has(dir)) return; test(dir, t => { - const source = fs.readFileSync(`test/v${v}/samples/${dir}/input.html`, 'utf-8'); - const expected = fs.readFileSync(`test/v${v}/samples/${dir}/output.html`, 'utf-8'); + const source_file = `test/v${v}/samples/${dir}/input.html`; + const output_file = `test/v${v}/samples/${dir}/output.html`; + const error_file = `test/v${v}/samples/${dir}/error.js`; - const actual = upgrader(source); + const source = fs.readFileSync(source_file, 'utf-8'); + + let actual; + let expected; + + try { + actual = upgrader(source); + expected = fs.readFileSync(output_file, 'utf-8'); + } catch (err) { + if (fs.existsSync(error_file)) { + const expected_error = require(path.resolve(error_file)); + + expected_error.frame = expected_error.frame + .replace('\n', '') + .replace(/^\t\t/gm, ''); + + if (err.code !== 'ENOENT') { + t.equal(serialize_error(err), serialize_error(expected_error)); + return; + } + } else { + throw err; + } + } + + if (fs.existsSync(error_file)) { + throw new Error(`expected an error, but got output instead`); + } t.equal(actual, expected); }); @@ -30,4 +59,12 @@ function testVersion(v, upgrader) { } if (versions.has('v2')) testVersion(2, v2); -if (versions.has('v3')) testVersion(3, v3); \ No newline at end of file +if (versions.has('v3')) testVersion(3, v3); + +function serialize_error(err) { + return JSON.stringify({ + message: err.message, + pos: err.pos, + frame: err.frame + }, null, ' '); +} \ No newline at end of file diff --git a/test/v3/samples/duplicate-declarations/error.js b/test/v3/samples/duplicate-declarations/error.js new file mode 100644 index 0000000..60f3963 --- /dev/null +++ b/test/v3/samples/duplicate-declarations/error.js @@ -0,0 +1,11 @@ +module.exports = { + message: "'foo' conflicts with existing declaration", + pos: 141, + frame: ` + 9: }, + 10: events: { + 11: foo(node, callback) { + ^ + 12: // code goes here + 13: }` +}; \ No newline at end of file diff --git a/test/v3/samples/duplicate-declarations/input.html b/test/v3/samples/duplicate-declarations/input.html new file mode 100644 index 0000000..ac3f8d4 --- /dev/null +++ b/test/v3/samples/duplicate-declarations/input.html @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d85329b..c3eced7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -257,6 +257,11 @@ kleur@^2.0.1, kleur@^2.0.2: resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +locate-character@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-2.0.5.tgz#f2d2614d49820ecb3c92d80d193b8db755f74c0f" + integrity sha512-n2GmejDXtOPBAZdIiEFy5dJ5N38xBCXLNOtw2WpB9kGh6pnrEuKlwYI+Tkpofc4wDtVXHtoAOJaMRlYG/oYaxg== + magic-string@^0.25.1: version "0.25.1" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.1.tgz#b1c248b399cd7485da0fe7385c2fc7011843266e" From c9f4a0581132d0c8642752fdbec11cf1c7bd996b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 6 Nov 2018 13:56:53 -0500 Subject: [PATCH 22/52] remove logging --- src/v3/scopes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/v3/scopes.js b/src/v3/scopes.js index 5f906cc..ab2a918 100644 --- a/src/v3/scopes.js +++ b/src/v3/scopes.js @@ -78,7 +78,6 @@ export class Scope { export function extract_names(param) { const names = []; - console.log(param.type); extractors[param.type](names, param); return names; } From 8c258246be06cf1bf2f52a83ad3d4ee0b0c7d02f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 7 Nov 2018 05:23:44 -0500 Subject: [PATCH 23/52] helpers --- src/v3/index.js | 21 ++++++++++++++++----- test/v3/samples/helpers/input.html | 27 +++++++++++++++++++++++++++ test/v3/samples/helpers/output.html | 15 +++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 test/v3/samples/helpers/input.html create mode 100644 test/v3/samples/helpers/output.html diff --git a/src/v3/index.js b/src/v3/index.js index e9a817f..9b5ce22 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -78,6 +78,7 @@ export function upgradeTemplate(source) { imports: [], methods: new Set(), computed: new Set(), + helpers: new Set(), declarations: new Set(), indent, indent_regex: new RegExp(`^${indent}`, 'gm'), @@ -105,14 +106,20 @@ export function upgradeTemplate(source) { default_export.declaration.properties.forEach(prop => { if (prop.key.name === 'methods') { - prop.value.properties.forEach(method => { - info.methods.add(method.key.name); + prop.value.properties.forEach(node => { + info.methods.add(node.key.name); }); } if (prop.key.name === 'computed') { - prop.value.properties.forEach(method => { - info.computed.add(method.key.name); + prop.value.properties.forEach(node => { + info.computed.add(node.key.name); + }); + } + + if (prop.key.name === 'helpers') { + prop.value.properties.forEach(node => { + info.helpers.add(node.key.name); }); } }); @@ -140,7 +147,11 @@ export function upgradeTemplate(source) { break; case 'events': - handle_registrants(prop.value.properties, info, 'event') + handle_registrants(prop.value.properties, info, 'event'); + break; + + case 'helpers': + handle_registrants(prop.value.properties, info, 'helpers'); break; case 'immutable': diff --git a/test/v3/samples/helpers/input.html b/test/v3/samples/helpers/input.html new file mode 100644 index 0000000..e0e56d0 --- /dev/null +++ b/test/v3/samples/helpers/input.html @@ -0,0 +1,27 @@ +

{qux(foo(bar(baz)))}

+ + \ No newline at end of file diff --git a/test/v3/samples/helpers/output.html b/test/v3/samples/helpers/output.html new file mode 100644 index 0000000..6b499d4 --- /dev/null +++ b/test/v3/samples/helpers/output.html @@ -0,0 +1,15 @@ + + +

{qux(foo(bar(baz)))}

\ No newline at end of file From ad2f80891c923a2c93203faa5ecbdca5925f84b8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 15:37:38 -0500 Subject: [PATCH 24/52] onprops/onupdate, improve CLI --- .gitignore | 3 +- src/cli.js | 47 ++++++++++++++++--- src/v3/handlers/methods.js | 2 + .../{oncreate.js => oncreate_ondestroy.js} | 0 src/v3/handlers/onstate_onupdate.js | 22 +++++++++ src/v3/index.js | 32 ++++++++++--- test/test.js | 2 + test/v3/samples/onstate/input.html | 11 +++++ test/v3/samples/onstate/output.html | 14 ++++++ test/v3/samples/onupdate/input.html | 11 +++++ test/v3/samples/onupdate/output.html | 14 ++++++ 11 files changed, 144 insertions(+), 14 deletions(-) rename src/v3/handlers/{oncreate.js => oncreate_ondestroy.js} (100%) create mode 100644 src/v3/handlers/onstate_onupdate.js create mode 100644 test/v3/samples/onstate/input.html create mode 100644 test/v3/samples/onstate/output.html create mode 100644 test/v3/samples/onupdate/input.html create mode 100644 test/v3/samples/onupdate/output.html diff --git a/.gitignore b/.gitignore index 1135475..15671ce 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules *.d.ts dist -yarn-error.log \ No newline at end of file +yarn-error.log +test/*/samples/_tmp \ No newline at end of file diff --git a/src/cli.js b/src/cli.js index 7034239..9cf087b 100644 --- a/src/cli.js +++ b/src/cli.js @@ -105,24 +105,35 @@ function get_tasks(items, in_dir, out_dir, arr = []) { let unchanged_count = 0; + const manual_edits_required = []; + const manual_edits_suggested = []; + const failed = []; + tasks.forEach(({ input, output, source }) => { + const relative = path.relative(process.cwd(), input); try { const result = upgrade.upgradeTemplate(source); - if (result.trim() === source.trim()) { + const code = v === 2 ? result : result.code; + + if (code.trim() === source.trim()) { unchanged_count += 1; + } else if (result.manual_edits_required) { + manual_edits_required.push(relative); + } else if (result.manual_edits_suggested) { + manual_edits_suggested.push(relative); } // we still write, even if unchanged, since the output dir // could be different to the input dir mkdirp(path.dirname(output)); - fs.writeFileSync(output, result); + fs.writeFileSync(output, code); } catch (error) { - console.error(c.bold.red(`Error transforming ${input}:`)); - console.error(c.red(error.message)); - - if (error.frame) { - console.error(error.frame); + if (error.name === 'UpgradeError') { + failed.push({ relative, error }); + } else { + console.error(c.bold.red(`Error transforming ${relative}:`)); + console.error(c.red(error.message)); } } }); @@ -134,6 +145,28 @@ function get_tasks(items, in_dir, out_dir, arr = []) { } console.error(c.cyan(message)); + + if (failed.length > 0) { + console.error(c.bold.red(`\nFailed to convert ${count(failed.length)}`)); + failed.forEach(failure => { + console.error(c.bold.red(`\n${failure.error.message}`)); + console.error(failure.relative); + + if (failure.error.frame) { + console.error(failure.error.frame); + } + }); + } + + if (manual_edits_required.length > 0) { + console.error(c.bold.red(`\nManual edits required for ${count(manual_edits_required.length)}`)); + console.error(manual_edits_required.join('\n')); + } + + if (manual_edits_suggested.length > 0) { + console.error(c.bold.magenta(`\nManual edits suggested for ${count(manual_edits_suggested.length)}`)); + console.error(manual_edits_suggested.join('\n')); + } } catch (err) { console.error(c.red(err.message)); } diff --git a/src/v3/handlers/methods.js b/src/v3/handlers/methods.js index 97a70d2..2de481d 100644 --- a/src/v3/handlers/methods.js +++ b/src/v3/handlers/methods.js @@ -39,4 +39,6 @@ export default function handle_methods(node, info) { if (statements.length > 0) { blocks.push(`${statements.join('\n')}`); } + + info.manual_edits_suggested = true; } \ No newline at end of file diff --git a/src/v3/handlers/oncreate.js b/src/v3/handlers/oncreate_ondestroy.js similarity index 100% rename from src/v3/handlers/oncreate.js rename to src/v3/handlers/oncreate_ondestroy.js diff --git a/src/v3/handlers/onstate_onupdate.js b/src/v3/handlers/onstate_onupdate.js new file mode 100644 index 0000000..b9d8227 --- /dev/null +++ b/src/v3/handlers/onstate_onupdate.js @@ -0,0 +1,22 @@ +import rewrite_this from './shared/rewrite_this.js'; + +export default function handle_onstate_onupdate(node, info, name) { + const { code, blocks, lifecycle_functions, indent, indent_regex } = info; + + lifecycle_functions.add(name); + + if (node.type === 'FunctionExpression') { + rewrite_this(node.body, info); + + const body = code.slice(node.body.start, node.body.end) + .replace(indent_regex, ''); + + blocks.push(`// [svelte-upgrade warning]\n${indent}// onprops and onupdate handlers behave\n${indent}// differently to their v2 counterparts\n${indent}${name}(${node.async ? `async ` : ``}() => ${body});`); + } + + else { + throw new Error(`TODO non-function-expression ${name}`); + } + + info.manual_edits_required = true; +} \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 9b5ce22..2878daa 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -5,7 +5,7 @@ import handle_components from './handlers/components.js'; import handle_computed from './handlers/computed.js'; import handle_data from './handlers/data.js'; import handle_methods from './handlers/methods.js'; -import handle_oncreate from './handlers/oncreate.js'; +import handle_oncreate_ondestroy from './handlers/oncreate_ondestroy.js'; import handle_on_directive from './handlers/on_directive'; import handle_use_directive from './handlers/use_directive'; import handle_registrants from './handlers/shared/handle_registrants.js'; @@ -14,6 +14,7 @@ import handle_setup from './handlers/setup.js'; import { find_declarations, get_code_frame } from './utils.js'; import { extract_names } from './scopes.js'; import rewrite_computed from './rewrite_computed.js'; +import handle_onstate_onupdate from './handlers/onstate_onupdate.js'; // We need to tell estree-walker that it should always // look for an `else` block, otherwise it might get @@ -49,6 +50,8 @@ const global_whitelist = new Set([ 'undefined', ]); +class UpgradeError extends Error {} + export function upgradeTemplate(source) { const code = new MagicString(source); const result = svelte.compile(source, { @@ -86,8 +89,12 @@ export function upgradeTemplate(source) { uses_dispatch: false, uses_this_properties: new Set(), + manual_edits_required: false, + manual_edits_suggested: false, + error(message, pos) { - const e = new Error(message); + const e = new UpgradeError(message); + e.name = 'UpgradeError'; e.pos = pos; e.frame = get_code_frame(source, pos); @@ -162,12 +169,20 @@ export function upgradeTemplate(source) { handle_methods(prop.value, info); break; - case 'oncreate': case 'onrender': - handle_oncreate(prop.value, info, 'onmount'); + case 'oncreate': case 'onrender': + handle_oncreate_ondestroy(prop.value, info, 'onmount'); break; case 'ondestroy': case 'onteardown': - handle_oncreate(prop.value, info, 'ondestroy'); + handle_oncreate_ondestroy(prop.value, info, 'ondestroy'); + break; + + case 'onstate': + handle_onstate_onupdate(prop.value, info, 'onprops'); + break; + + case 'onupdate': + handle_onstate_onupdate(prop.value, info, 'onupdate'); break; case 'preload': @@ -248,6 +263,7 @@ export function upgradeTemplate(source) { if (info.uses_this) { script_sections.unshift(`// [svelte-upgrade suggestion]\n${indent}// manually refactor all references to __this\n${indent}const __this = {};`); + info.manual_edits_suggested = true; } if (info.imports.length) { @@ -332,5 +348,9 @@ export function upgradeTemplate(source) { upgraded = `\n\n${upgraded}`; } - return upgraded.trim(); + return { + code: upgraded.trim(), + manual_edits_required: info.manual_edits_required, + manual_edits_suggested: info.manual_edits_suggested + }; } \ No newline at end of file diff --git a/test/test.js b/test/test.js index 184a60d..cf8f573 100644 --- a/test/test.js +++ b/test/test.js @@ -31,6 +31,8 @@ function testVersion(v, upgrader) { try { actual = upgrader(source); + if (v === 3) actual = actual.code; + expected = fs.readFileSync(output_file, 'utf-8'); } catch (err) { if (fs.existsSync(error_file)) { diff --git a/test/v3/samples/onstate/input.html b/test/v3/samples/onstate/input.html new file mode 100644 index 0000000..b5f0844 --- /dev/null +++ b/test/v3/samples/onstate/input.html @@ -0,0 +1,11 @@ +

component with onstate

+ + \ No newline at end of file diff --git a/test/v3/samples/onstate/output.html b/test/v3/samples/onstate/output.html new file mode 100644 index 0000000..928beb9 --- /dev/null +++ b/test/v3/samples/onstate/output.html @@ -0,0 +1,14 @@ + + +

component with onstate

\ No newline at end of file diff --git a/test/v3/samples/onupdate/input.html b/test/v3/samples/onupdate/input.html new file mode 100644 index 0000000..2422937 --- /dev/null +++ b/test/v3/samples/onupdate/input.html @@ -0,0 +1,11 @@ +

component with onupdate

+ + \ No newline at end of file diff --git a/test/v3/samples/onupdate/output.html b/test/v3/samples/onupdate/output.html new file mode 100644 index 0000000..edccbc3 --- /dev/null +++ b/test/v3/samples/onupdate/output.html @@ -0,0 +1,14 @@ + + +

component with onupdate

\ No newline at end of file From 1f5fd60238b01f1afbcd3c9ed07fa345b9389eea Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 15:49:02 -0500 Subject: [PATCH 25/52] tweaks --- package.json | 3 ++- rollup.config.js | 3 ++- src/v3/handlers/shared/handle_registrants.js | 2 +- src/v3/handlers/shared/rewrite_this.js | 1 + yarn.lock | 18 ++++++++++++++++++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 45c6ae4..84f2e32 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,13 @@ "esm": "^3.0.84", "rollup": "^0.67.0", "rollup-plugin-json": "^3.1.0", + "source-map-support": "^0.5.9", "tape-modern": "^1.1.1" }, "scripts": { "build": "rollup -c", "dev": "rollup -cw", - "test": "node -r esm test/test.js", + "test": "node -r source-map-support/register -r esm test/test.js", "prepublishOnly": "npm test && npm run build" }, "license": "LIL", diff --git a/rollup.config.js b/rollup.config.js index 4073847..7aae66c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -5,7 +5,8 @@ export default { input: ['src/index.js', 'src/cli.js'], output: { dir: 'dist', - format: 'cjs' + format: 'cjs', + sourcemap: true }, experimentalCodeSplitting: true, external: Object.keys(pkg.dependencies), diff --git a/src/v3/handlers/shared/handle_registrants.js b/src/v3/handlers/shared/handle_registrants.js index a03374c..3b807d7 100644 --- a/src/v3/handlers/shared/handle_registrants.js +++ b/src/v3/handlers/shared/handle_registrants.js @@ -25,7 +25,7 @@ export default function handle_registrants(registrants, info, type) { } else if (registrant.value.type === 'Identifier') { alias_registration(registrant, info, statements, type); } else { - error(`can only convert ${type}s that are function expressions or references`, method); + error(`can only convert ${type}s that are function expressions or references`, registrant); } }); diff --git a/src/v3/handlers/shared/rewrite_this.js b/src/v3/handlers/shared/rewrite_this.js index f574312..4cc1816 100644 --- a/src/v3/handlers/shared/rewrite_this.js +++ b/src/v3/handlers/shared/rewrite_this.js @@ -14,6 +14,7 @@ export default function rewrite_this(node, info, is_event_handler, replacement = if (child.type === 'CallExpression') { if (is_set(child.callee, is_event_handler)) { rewrite_set(child, info); + return this.skip(); } // TODO optimise get diff --git a/yarn.lock b/yarn.lock index c3eced7..a38489b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -56,6 +56,11 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -429,6 +434,19 @@ sisteransi@^1.0.0: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c" integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ== +source-map-support@^0.5.9: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + sourcemap-codec@^1.4.1: version "1.4.3" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.3.tgz#0ba615b73ec35112f63c2f2d9e7c3f87282b0e33" From d5fcd12e3a720fca877c63d062f620c512820114 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 16:03:10 -0500 Subject: [PATCH 26/52] handle computed props without destructuring --- src/v3/handlers/computed.js | 70 +++++++++++-------- .../samples/computed-whole-state/input.html | 9 +++ .../samples/computed-whole-state/output.html | 9 +++ 3 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 test/v3/samples/computed-whole-state/input.html create mode 100644 test/v3/samples/computed-whole-state/output.html diff --git a/src/v3/handlers/computed.js b/src/v3/handlers/computed.js index 58726d3..1c427c2 100644 --- a/src/v3/handlers/computed.js +++ b/src/v3/handlers/computed.js @@ -6,36 +6,46 @@ export default function handle_computed(node, info) { const { props, code, blocks, indent } = info; node.properties.forEach(computed => { - const declarations = computed.value.params[0].properties - .filter(param => param.value.type !== 'Identifier') - .map(param => { - const { name } = param.key; - const lhs = code.slice(param.value.start, param.value.end); - const rhs = info.computed.has(name) ? `${name}()` : name; - return `const ${lhs} = ${rhs};` - }); + let statements = []; - // need to rewrite x => x() if x is computed - let { map, scope } = create_scopes(computed.value.body); - walk(computed.value.body, { - enter(node, parent) { - if (map.has(node)) { - scope = map.get(node); - } + if (computed.value.params[0].type === 'ObjectPattern') { + statements = computed.value.params[0].properties + .filter(param => param.value.type !== 'Identifier') + .map(param => { + const { name } = param.key; + const lhs = code.slice(param.value.start, param.value.end); + const rhs = info.computed.has(name) ? `${name}()` : name; + return `const ${lhs} = ${rhs};` + }); - if (is_reference(node, parent)) { - if (info.computed.has(node.name)) { - code.appendLeft(node.end, '()'); + // need to rewrite x => x() if x is computed + let { map, scope } = create_scopes(computed.value.body); + walk(computed.value.body, { + enter(node, parent) { + if (map.has(node)) { + scope = map.get(node); } - } - }, - leave(node) { - if (map.has(node)) { - scope = scope.parent; + if (is_reference(node, parent)) { + if (info.computed.has(node.name)) { + code.appendLeft(node.end, '()'); + } + } + }, + + leave(node) { + if (map.has(node)) { + scope = scope.parent; + } } - } - }); + }); + } else { + statements = [ + `// [svelte-upgrade warning]\n${indent}${indent}// this function needs to be manually rewritten` + ]; + + info.manual_edit_required = true; + } const implicit_return = ( computed.value.type === 'ArrowFunctionExpression' && @@ -44,14 +54,16 @@ export default function handle_computed(node, info) { if (implicit_return) { const expression = code.slice(computed.value.body.start, computed.value.body.end); - const statements = declarations.concat(`return ${expression};`).join(`\n${indent}${indent}`); + statements.push(`return ${expression};`); + + const body = statements.join(`\n${indent}${indent}`); - blocks.push(`function ${computed.key.name}() {\n${indent}${indent}${statements}\n${indent}}`); + blocks.push(`function ${computed.key.name}() {\n${indent}${indent}${body}\n${indent}}`); } else { - if (declarations.length) { + if (statements.length) { const i = indent + indent + indent + indent; - const declaration_block = declarations.join(`\n${i}`); + const declaration_block = statements.join(`\n${i}`); code.appendLeft(computed.value.body.start + 1, `\n${i}${declaration_block}`); } diff --git a/test/v3/samples/computed-whole-state/input.html b/test/v3/samples/computed-whole-state/input.html new file mode 100644 index 0000000..f8505a1 --- /dev/null +++ b/test/v3/samples/computed-whole-state/input.html @@ -0,0 +1,9 @@ +

{b}

+ + \ No newline at end of file diff --git a/test/v3/samples/computed-whole-state/output.html b/test/v3/samples/computed-whole-state/output.html new file mode 100644 index 0000000..2c80bc6 --- /dev/null +++ b/test/v3/samples/computed-whole-state/output.html @@ -0,0 +1,9 @@ + + +

{b()}

\ No newline at end of file From 5ef584b53ab3933e28823d1fb0d8cc09d5367cdc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 16:17:22 -0500 Subject: [PATCH 27/52] handle non-function registrants --- src/v3/handlers/shared/handle_registrants.js | 18 ++++++++++++------ src/v3/index.js | 2 +- test/v3/samples/helper-non-function/input.html | 13 +++++++++++++ .../v3/samples/helper-non-function/output.html | 11 +++++++++++ 4 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 test/v3/samples/helper-non-function/input.html create mode 100644 test/v3/samples/helper-non-function/output.html diff --git a/src/v3/handlers/shared/handle_registrants.js b/src/v3/handlers/shared/handle_registrants.js index 3b807d7..1ace38d 100644 --- a/src/v3/handlers/shared/handle_registrants.js +++ b/src/v3/handlers/shared/handle_registrants.js @@ -7,8 +7,10 @@ export default function handle_registrants(registrants, info, type) { const statements = []; registrants.forEach(registrant => { - if (registrant.value.type === 'FunctionExpression') { - const { params, body } = registrant.value; + const { key, value } = registrant; + + if (value.type === 'FunctionExpression') { + const { params, body } = value; rewrite_this(body, info); @@ -20,12 +22,16 @@ export default function handle_registrants(registrants, info, type) { ? `(${code.slice(params[0].start, params[params.length - 1].end)})` : '()'; - add_declaration(registrant.key, info); - blocks.push(`function ${registrant.key.name}${args} ${str}`); - } else if (registrant.value.type === 'Identifier') { + add_declaration(key, info); + blocks.push(`function ${key.name}${args} ${str}`); + } else if (value.type === 'Identifier') { alias_registration(registrant, info, statements, type); } else { - error(`can only convert ${type}s that are function expressions or references`, registrant); + const str = code.slice(value.start, value.end) + .replace(info.indent_regex, '') + .replace(info.indent_regex, ''); + + blocks.push(`const ${key.name} = ${str};`); } }); diff --git a/src/v3/index.js b/src/v3/index.js index 2878daa..0c98b5a 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -158,7 +158,7 @@ export function upgradeTemplate(source) { break; case 'helpers': - handle_registrants(prop.value.properties, info, 'helpers'); + handle_registrants(prop.value.properties, info, 'helper'); break; case 'immutable': diff --git a/test/v3/samples/helper-non-function/input.html b/test/v3/samples/helper-non-function/input.html new file mode 100644 index 0000000..7eac2f2 --- /dev/null +++ b/test/v3/samples/helper-non-function/input.html @@ -0,0 +1,13 @@ +{Math.min(x, 5)} + + diff --git a/test/v3/samples/helper-non-function/output.html b/test/v3/samples/helper-non-function/output.html new file mode 100644 index 0000000..ca14158 --- /dev/null +++ b/test/v3/samples/helper-non-function/output.html @@ -0,0 +1,11 @@ + + +{Math.min(x, 5)} \ No newline at end of file From 9755366de6e3c74c128e4ca1dc0477bfa9815b35 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 16:25:25 -0500 Subject: [PATCH 28/52] handle rest elements in computed properties --- src/v3/handlers/computed.js | 19 ++++++++++++------- .../computed-whole-state-rest/input.html | 9 +++++++++ .../computed-whole-state-rest/output.html | 9 +++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 test/v3/samples/computed-whole-state-rest/input.html create mode 100644 test/v3/samples/computed-whole-state-rest/output.html diff --git a/src/v3/handlers/computed.js b/src/v3/handlers/computed.js index 1c427c2..c6a8049 100644 --- a/src/v3/handlers/computed.js +++ b/src/v3/handlers/computed.js @@ -8,7 +8,18 @@ export default function handle_computed(node, info) { node.properties.forEach(computed => { let statements = []; - if (computed.value.params[0].type === 'ObjectPattern') { + const uses_whole_state = ( + computed.value.params[0].type !== 'ObjectPattern' || + computed.value.params[0].properties.some(x => x.type === 'RestElement') + ); + + if (uses_whole_state) { + statements = [ + `// [svelte-upgrade warning]\n${indent}${indent}// this function needs to be manually rewritten` + ]; + + info.manual_edit_required = true; + } else { statements = computed.value.params[0].properties .filter(param => param.value.type !== 'Identifier') .map(param => { @@ -39,12 +50,6 @@ export default function handle_computed(node, info) { } } }); - } else { - statements = [ - `// [svelte-upgrade warning]\n${indent}${indent}// this function needs to be manually rewritten` - ]; - - info.manual_edit_required = true; } const implicit_return = ( diff --git a/test/v3/samples/computed-whole-state-rest/input.html b/test/v3/samples/computed-whole-state-rest/input.html new file mode 100644 index 0000000..ffc63db --- /dev/null +++ b/test/v3/samples/computed-whole-state-rest/input.html @@ -0,0 +1,9 @@ +
{JSON.stringify(props)}
+ + \ No newline at end of file diff --git a/test/v3/samples/computed-whole-state-rest/output.html b/test/v3/samples/computed-whole-state-rest/output.html new file mode 100644 index 0000000..34f0009 --- /dev/null +++ b/test/v3/samples/computed-whole-state-rest/output.html @@ -0,0 +1,9 @@ + + +
{JSON.stringify(props())}
\ No newline at end of file From f347924c4113f9ff9363da6b5bca4490f205116c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 16:39:55 -0500 Subject: [PATCH 29/52] handle store --- .gitignore | 3 ++- src/v3/handlers/setup.js | 4 +++- src/v3/handlers/store.js | 10 ++++++++++ src/v3/index.js | 7 ++++++- test/test.js | 3 ++- test/v3/samples/store/input.html | 13 +++++++++++++ test/v3/samples/store/output.html | 16 ++++++++++++++++ 7 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 src/v3/handlers/store.js create mode 100644 test/v3/samples/store/input.html create mode 100644 test/v3/samples/store/output.html diff --git a/.gitignore b/.gitignore index 15671ce..28936bb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules *.d.ts dist yarn-error.log -test/*/samples/_tmp \ No newline at end of file +test/*/samples/_tmp +_actual.html \ No newline at end of file diff --git a/src/v3/handlers/setup.js b/src/v3/handlers/setup.js index 4e8fff7..15719c2 100644 --- a/src/v3/handlers/setup.js +++ b/src/v3/handlers/setup.js @@ -1,8 +1,10 @@ -export default function handle_methods(node, info) { +export default function handle_setup(node, info) { const { shared_blocks, code, indent } = info; const setup = code.slice(node.start, node.end) .replace(info.indent_regex, ''); shared_blocks.push(`/*\n${indent}svelte-upgrade cannot automatically transform this code —\n${indent}it must be updated manually\n\n${indent}${setup}\n${indent}*/`); + + info.manual_edits_required = true; } \ No newline at end of file diff --git a/src/v3/handlers/store.js b/src/v3/handlers/store.js new file mode 100644 index 0000000..250bf11 --- /dev/null +++ b/src/v3/handlers/store.js @@ -0,0 +1,10 @@ +export default function handle_store(node, info) { + const { blocks, code, indent } = info; + + const setup = code.slice(node.start, node.end) + .replace(info.indent_regex, ''); + + blocks.push(`/*\n${indent}svelte-upgrade cannot automatically transform this code —\n${indent}it must be updated manually\n\n${indent}${setup}\n${indent}*/`); + + info.manual_edits_required = true; +} \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 0c98b5a..7b38020 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -11,6 +11,7 @@ import handle_use_directive from './handlers/use_directive'; import handle_registrants from './handlers/shared/handle_registrants.js'; import handle_preload from './handlers/preload.js'; import handle_setup from './handlers/setup.js'; +import handle_store from './handlers/store.js'; import { find_declarations, get_code_frame } from './utils.js'; import { extract_names } from './scopes.js'; import rewrite_computed from './rewrite_computed.js'; @@ -67,7 +68,7 @@ export function upgradeTemplate(source) { const props = new Map(); result.stats.props.forEach(prop => { - if (!global_whitelist.has(prop)) { + if (!global_whitelist.has(prop) && prop[0] !== '$') { props.set(prop, 'undefined'); } }); @@ -193,6 +194,10 @@ export function upgradeTemplate(source) { handle_setup(prop, info); break; + case 'store': + handle_store(prop, info); + break; + case 'tag': tag = prop.value.value; break; diff --git a/test/test.js b/test/test.js index cf8f573..7896a00 100644 --- a/test/test.js +++ b/test/test.js @@ -33,6 +33,7 @@ function testVersion(v, upgrader) { actual = upgrader(source); if (v === 3) actual = actual.code; + fs.writeFileSync(output_file.replace('output.html', '_actual.html'), actual); expected = fs.readFileSync(output_file, 'utf-8'); } catch (err) { if (fs.existsSync(error_file)) { @@ -55,7 +56,7 @@ function testVersion(v, upgrader) { throw new Error(`expected an error, but got output instead`); } - t.equal(actual, expected); + t.equal(actual.trim(), expected.trim()); }); }); } diff --git a/test/v3/samples/store/input.html b/test/v3/samples/store/input.html new file mode 100644 index 0000000..8305ba3 --- /dev/null +++ b/test/v3/samples/store/input.html @@ -0,0 +1,13 @@ +

Hello {$name}!

+ + diff --git a/test/v3/samples/store/output.html b/test/v3/samples/store/output.html new file mode 100644 index 0000000..2ca57f9 --- /dev/null +++ b/test/v3/samples/store/output.html @@ -0,0 +1,16 @@ + + +

Hello {$name}!

\ No newline at end of file From 08afbdcf00f35f76814ab5e1ea7dd0b4664452a4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 16:49:09 -0500 Subject: [PATCH 30/52] handle non-function-expression lifecycle hooks --- src/v3/handlers/oncreate_ondestroy.js | 3 ++- src/v3/handlers/onstate_onupdate.js | 3 ++- test/v3/samples/oncreate-arrow/input.html | 7 +++++++ test/v3/samples/oncreate-arrow/output.html | 7 +++++++ 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 test/v3/samples/oncreate-arrow/input.html create mode 100644 test/v3/samples/oncreate-arrow/output.html diff --git a/src/v3/handlers/oncreate_ondestroy.js b/src/v3/handlers/oncreate_ondestroy.js index 5ad06fb..4ad900b 100644 --- a/src/v3/handlers/oncreate_ondestroy.js +++ b/src/v3/handlers/oncreate_ondestroy.js @@ -13,6 +13,7 @@ export default function handle_oncreate_ondestroy(node, info, name) { } else { - throw new Error(`TODO non-function-expression ${name}`); + const body = code.slice(node.start, node.end).replace(indent_regex, ''); + blocks.push(`${name}(${body});`); } } \ No newline at end of file diff --git a/src/v3/handlers/onstate_onupdate.js b/src/v3/handlers/onstate_onupdate.js index b9d8227..c225bf5 100644 --- a/src/v3/handlers/onstate_onupdate.js +++ b/src/v3/handlers/onstate_onupdate.js @@ -15,7 +15,8 @@ export default function handle_onstate_onupdate(node, info, name) { } else { - throw new Error(`TODO non-function-expression ${name}`); + const body = code.slice(node.start, node.end).replace(indent_regex, ''); + blocks.push(`${name}(${body});`); } info.manual_edits_required = true; diff --git a/test/v3/samples/oncreate-arrow/input.html b/test/v3/samples/oncreate-arrow/input.html new file mode 100644 index 0000000..6a3f4c4 --- /dev/null +++ b/test/v3/samples/oncreate-arrow/input.html @@ -0,0 +1,7 @@ + diff --git a/test/v3/samples/oncreate-arrow/output.html b/test/v3/samples/oncreate-arrow/output.html new file mode 100644 index 0000000..4b075b0 --- /dev/null +++ b/test/v3/samples/oncreate-arrow/output.html @@ -0,0 +1,7 @@ + From 47922f662908a794d31f495fc739483e8668bb63 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 17:08:02 -0500 Subject: [PATCH 31/52] fix declaration finding --- src/v3/utils.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/v3/utils.js b/src/v3/utils.js index 3a7e8f6..b2c8326 100644 --- a/src/v3/utils.js +++ b/src/v3/utils.js @@ -17,6 +17,7 @@ export function find_declarations(body, declarations) { else if (node.type === 'FunctionDeclaration') { declarations.add(node.id.name); + this.skip(); } else if (node.type === 'VariableDeclaration') { @@ -26,6 +27,10 @@ export function find_declarations(body, declarations) { }); }); } + + if (node.type === 'FunctionExpression') { + this.skip(); + } } }); From d506b887066bb4ca3fde72f6059a5eda752ad0bf Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 17:10:04 -0500 Subject: [PATCH 32/52] fix declaration finding --- src/v3/utils.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/v3/utils.js b/src/v3/utils.js index b2c8326..8e4b7f8 100644 --- a/src/v3/utils.js +++ b/src/v3/utils.js @@ -3,6 +3,8 @@ import { extract_names } from './scopes'; import { locate } from 'locate-character'; export function find_declarations(body, declarations) { + let block_depth = 0; + walk(body, { enter(node, parent) { if (node.type === 'ImportDeclaration') { @@ -21,16 +23,28 @@ export function find_declarations(body, declarations) { } else if (node.type === 'VariableDeclaration') { - node.declarations.forEach(declarator => { - extract_names(declarator.id).forEach(name => { - declarations.add(name); + if (node.kind === 'var' || block_depth === 0) { + node.declarations.forEach(declarator => { + extract_names(declarator.id).forEach(name => { + declarations.add(name); + }); }); - }); + } } if (node.type === 'FunctionExpression') { this.skip(); } + + if (node.type === 'BlockStatement') { + block_depth += 1; + } + }, + + leave(node) { + if (node.type === 'BlockStatement') { + block_depth -= 1; + } } }); From 64e607fca50bd47aaf92ea207617dccec3d8f34c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 Nov 2018 18:14:28 -0500 Subject: [PATCH 33/52] minor fixes --- src/v3/handlers/computed.js | 2 +- src/v3/handlers/data.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/v3/handlers/computed.js b/src/v3/handlers/computed.js index c6a8049..1537008 100644 --- a/src/v3/handlers/computed.js +++ b/src/v3/handlers/computed.js @@ -18,7 +18,7 @@ export default function handle_computed(node, info) { `// [svelte-upgrade warning]\n${indent}${indent}// this function needs to be manually rewritten` ]; - info.manual_edit_required = true; + info.manual_edits_required = true; } else { statements = computed.value.params[0].properties .filter(param => param.value.type !== 'Identifier') diff --git a/src/v3/handlers/data.js b/src/v3/handlers/data.js index b08fe3a..ee1d711 100644 --- a/src/v3/handlers/data.js +++ b/src/v3/handlers/data.js @@ -12,6 +12,10 @@ export default function handle_data(node, info) { if (node.body.type === 'BlockStatement') { walk(node.body, { enter(child, parent) { + if (/Function/.test(child.type)) { + this.skip(); + } + if (child.type === 'ReturnStatement') { if (parent !== node.body) { error(`can only convert data with a top-level return statement`, child.start); From 84e779b0f750ebaf969305057f5e349415ca422c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Nov 2018 08:35:36 -0500 Subject: [PATCH 34/52] indent data --- src/v3/handlers/data.js | 9 +++++++-- test/v3/samples/data-indentation/input.html | 17 +++++++++++++++++ test/v3/samples/data-indentation/output.html | 11 +++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 test/v3/samples/data-indentation/input.html create mode 100644 test/v3/samples/data-indentation/output.html diff --git a/src/v3/handlers/data.js b/src/v3/handlers/data.js index ee1d711..4414cc2 100644 --- a/src/v3/handlers/data.js +++ b/src/v3/handlers/data.js @@ -1,7 +1,7 @@ import { walk } from 'estree-walker'; export default function handle_data(node, info) { - const { props, code, error } = info; + const { props, code, error, indent_regex } = info; if (!/FunctionExpression/.test(node.type)) { error(`can only convert 'data' if it is a function expression or arrow function expression`, node.start); @@ -48,6 +48,11 @@ export default function handle_data(node, info) { } returned.properties.forEach(prop => { - props.set(prop.key.name, code.original.slice(prop.value.start, prop.value.end)); + const body = code.original.slice(prop.value.start, prop.value.end) + .replace(indent_regex, '') + .replace(indent_regex, '') + .replace(indent_regex, ''); + + props.set(prop.key.name, body); }); } \ No newline at end of file diff --git a/test/v3/samples/data-indentation/input.html b/test/v3/samples/data-indentation/input.html new file mode 100644 index 0000000..6190fcd --- /dev/null +++ b/test/v3/samples/data-indentation/input.html @@ -0,0 +1,17 @@ +{#each items as item} +

{item}

+{/each} + + \ No newline at end of file diff --git a/test/v3/samples/data-indentation/output.html b/test/v3/samples/data-indentation/output.html new file mode 100644 index 0000000..afa0ba5 --- /dev/null +++ b/test/v3/samples/data-indentation/output.html @@ -0,0 +1,11 @@ + + +{#each items as item} +

{item}

+{/each} \ No newline at end of file From 534f61be9718beea3fd36b9f885e8d1fdc7b9783 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Nov 2018 08:52:51 -0500 Subject: [PATCH 35/52] simplify event handlers with only the event arg --- src/v3/handlers/on_directive.js | 19 ++++++++++--------- .../event-handler-event-arg/input.html | 13 +++++++++++++ .../event-handler-event-arg/output.html | 13 +++++++++++++ 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 test/v3/samples/event-handler-event-arg/input.html create mode 100644 test/v3/samples/event-handler-event-arg/output.html diff --git a/src/v3/handlers/on_directive.js b/src/v3/handlers/on_directive.js index 8bb6e15..662008a 100644 --- a/src/v3/handlers/on_directive.js +++ b/src/v3/handlers/on_directive.js @@ -7,9 +7,10 @@ export default function handle_on_directive(node, info, parent) { if (!node.expression) return; const { code } = info; + const { arguments: args, callee, start, end } = node.expression; - if (node.expression.arguments.length === 0) { - code.remove(node.expression.callee.end, node.expression.end); + if (args.length === 0 || (args.length === 1 && args[0].name === 'event')) { + code.remove(callee.end, end); } else { const uses_event = find_event(node.expression); @@ -19,20 +20,20 @@ export default function handle_on_directive(node, info, parent) { rewrite_this(node.expression, info, true, this_replacement); - code.prependRight(node.expression.start, uses_event ? `event => ` : `() => `); + code.prependRight(start, uses_event ? `event => ` : `() => `); } - let a = node.expression.start; + let a = start; while (code.original[a - 1] !== '=') a -= 1; - const has_quote = a !== node.expression.start; + const has_quote = a !== start; const needs_quote = !has_quote && ( - /\s/.test(code.slice(node.expression.start, node.expression.end)) || - node.expression.arguments.length > 0 + /\s/.test(code.slice(start, end)) || + args.length > 0 ); - code.appendLeft(node.expression.start, needs_quote ? '"{' : '{'); - code.prependRight(node.expression.end, needs_quote ? '}"' : '}'); + code.appendLeft(start, needs_quote ? '"{' : '{'); + code.prependRight(end, needs_quote ? '}"' : '}'); } function find_event(expression) { diff --git a/test/v3/samples/event-handler-event-arg/input.html b/test/v3/samples/event-handler-event-arg/input.html new file mode 100644 index 0000000..1bce044 --- /dev/null +++ b/test/v3/samples/event-handler-event-arg/input.html @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/event-handler-event-arg/output.html b/test/v3/samples/event-handler-event-arg/output.html new file mode 100644 index 0000000..749aab2 --- /dev/null +++ b/test/v3/samples/event-handler-event-arg/output.html @@ -0,0 +1,13 @@ + + + \ No newline at end of file From 1b5711292ac196b8761e1d4f213015a750292d9b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Nov 2018 09:21:33 -0500 Subject: [PATCH 36/52] rewrite this.get --- src/v3/handlers/shared/rewrite_get.js | 27 +++++++++++++++++++++++++ src/v3/handlers/shared/rewrite_this.js | 22 ++++++++++++++------ src/v3/index.js | 14 ++++++++++++- test/v3/samples/get-complex/input.html | 8 ++++++++ test/v3/samples/get-complex/output.html | 17 ++++++++++++++++ test/v3/samples/get/input.html | 12 +++++++++++ test/v3/samples/get/output.html | 13 ++++++++++++ 7 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/v3/handlers/shared/rewrite_get.js create mode 100644 test/v3/samples/get-complex/input.html create mode 100644 test/v3/samples/get-complex/output.html create mode 100644 test/v3/samples/get/input.html create mode 100644 test/v3/samples/get/output.html diff --git a/src/v3/handlers/shared/rewrite_get.js b/src/v3/handlers/shared/rewrite_get.js new file mode 100644 index 0000000..6a2ad32 --- /dev/null +++ b/src/v3/handlers/shared/rewrite_get.js @@ -0,0 +1,27 @@ +export default function rewrite_get(node, parent, info) { + if (node.id.type === 'ObjectPattern') { + node.id.properties.forEach(prop => { + if (!info.props.has(prop.key.name)) { + info.props.set(prop.key.name, 'undefined'); + } + }); + + if (node.id.properties.every(node => node.shorthand)) { + if (parent.declarations.length !== 1) { + throw new Error(`TODO handle this.get() among other declarators`); + } + + let a = parent.start; + while (/\s/.test(info.source[a - 1])) a -= 1; + + info.code.remove(a, parent.end); + return; + } + } + + const { start, end } = node.init.callee.object; + info.code.overwrite(start, end, '__this'); + + info.uses_this = true; + info.uses_this_properties.add('get'); +} \ No newline at end of file diff --git a/src/v3/handlers/shared/rewrite_this.js b/src/v3/handlers/shared/rewrite_this.js index 4cc1816..e98b9e2 100644 --- a/src/v3/handlers/shared/rewrite_this.js +++ b/src/v3/handlers/shared/rewrite_this.js @@ -1,18 +1,28 @@ import { walk } from 'estree-walker'; -import rewrite_set from './rewrite_set'; +import rewrite_set from './rewrite_set.js'; +import rewrite_get from './rewrite_get.js'; // TODO rename this function, it does more than rewrite `this` export default function rewrite_this(node, info, is_event_handler, replacement = '__this') { const { code, methods } = info; walk(node, { - enter(child) { + enter(child, parent) { if (/^Function/.test(child.type)) { this.skip(); } + if (child.type === 'VariableDeclarator') { + if (child.init && child.init.type === 'CallExpression') { + if (is_method(child.init.callee, 'get')) { + rewrite_get(child, parent, info); + return this.skip(); + } + } + } + if (child.type === 'CallExpression') { - if (is_set(child.callee, is_event_handler)) { + if (is_method(child.callee, 'set', is_event_handler)) { rewrite_set(child, info); return this.skip(); } @@ -54,14 +64,14 @@ function is_this_property(node) { ); } -function is_set(callee, is_event_handler) { +function is_method(callee, name, is_event_handler) { if (is_event_handler) { - return callee.type === 'Identifier' && callee.name === 'set'; + return callee.type === 'Identifier' && callee.name === name; } return ( is_this_property(callee) && - callee.property.name === 'set' && + callee.property.name === name && !callee.property.computed ); } \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 7b38020..78eac83 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -74,6 +74,7 @@ export function upgradeTemplate(source) { }); const info = { + source, code, lifecycle_functions: new Set(), props, @@ -267,7 +268,18 @@ export function upgradeTemplate(source) { } if (info.uses_this) { - script_sections.unshift(`// [svelte-upgrade suggestion]\n${indent}// manually refactor all references to __this\n${indent}const __this = {};`); + const this_props = []; + + if (info.uses_this_properties.has('get')) { + const props = Array.from(info.props.keys()); + this_props.push(`get: () => ({ ${props.join(', ')} })`); + } + + const rhs = this_props.length + ? `{\n${indent}${indent}${this_props.join(`\n${indent}${indent}`)}\n${indent}}` + : `{}`; + + script_sections.unshift(`// [svelte-upgrade suggestion]\n${indent}// manually refactor all references to __this\n${indent}const __this = ${rhs};`); info.manual_edits_suggested = true; } diff --git a/test/v3/samples/get-complex/input.html b/test/v3/samples/get-complex/input.html new file mode 100644 index 0000000..cb6a92c --- /dev/null +++ b/test/v3/samples/get-complex/input.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/test/v3/samples/get-complex/output.html b/test/v3/samples/get-complex/output.html new file mode 100644 index 0000000..41bff9d --- /dev/null +++ b/test/v3/samples/get-complex/output.html @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/test/v3/samples/get/input.html b/test/v3/samples/get/input.html new file mode 100644 index 0000000..ded302f --- /dev/null +++ b/test/v3/samples/get/input.html @@ -0,0 +1,12 @@ +{#each items as item} +

{item}

+{/each} + + \ No newline at end of file diff --git a/test/v3/samples/get/output.html b/test/v3/samples/get/output.html new file mode 100644 index 0000000..a364a0f --- /dev/null +++ b/test/v3/samples/get/output.html @@ -0,0 +1,13 @@ + + +{#each items as item} +

{item}

+{/each} \ No newline at end of file From 705bb84e5988251dd7e45cf2a62dd1ecb1996dd8 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Nov 2018 09:27:06 -0500 Subject: [PATCH 37/52] preserve EOF newlines --- src/v3/index.js | 4 +++- test/test.js | 2 +- test/v3/samples/preserve-eof-newline/input.html | 1 + test/v3/samples/preserve-eof-newline/output.html | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 test/v3/samples/preserve-eof-newline/input.html create mode 100644 test/v3/samples/preserve-eof-newline/output.html diff --git a/src/v3/index.js b/src/v3/index.js index 78eac83..e98f9c1 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -365,8 +365,10 @@ export function upgradeTemplate(source) { upgraded = `\n\n${upgraded}`; } + const eof_newline = /(\r?\n)?$/.exec(source)[1]; + return { - code: upgraded.trim(), + code: upgraded.trim() + eof_newline, manual_edits_required: info.manual_edits_required, manual_edits_suggested: info.manual_edits_suggested }; diff --git a/test/test.js b/test/test.js index 7896a00..4d751a9 100644 --- a/test/test.js +++ b/test/test.js @@ -56,7 +56,7 @@ function testVersion(v, upgrader) { throw new Error(`expected an error, but got output instead`); } - t.equal(actual.trim(), expected.trim()); + t.equal(actual, expected); }); }); } diff --git a/test/v3/samples/preserve-eof-newline/input.html b/test/v3/samples/preserve-eof-newline/input.html new file mode 100644 index 0000000..597ecf5 --- /dev/null +++ b/test/v3/samples/preserve-eof-newline/input.html @@ -0,0 +1 @@ +

Hello world!

diff --git a/test/v3/samples/preserve-eof-newline/output.html b/test/v3/samples/preserve-eof-newline/output.html new file mode 100644 index 0000000..597ecf5 --- /dev/null +++ b/test/v3/samples/preserve-eof-newline/output.html @@ -0,0 +1 @@ +

Hello world!

From ca89d223b4735ea4142277434aa87903d8b743ab Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Nov 2018 09:42:09 -0500 Subject: [PATCH 38/52] oops --- src/v3/index.js | 2 +- test/v3/samples/helper-non-function/output.html | 2 +- test/v3/samples/store/output.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/v3/index.js b/src/v3/index.js index e98f9c1..8e81635 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -365,7 +365,7 @@ export function upgradeTemplate(source) { upgraded = `\n\n${upgraded}`; } - const eof_newline = /(\r?\n)?$/.exec(source)[1]; + const eof_newline = /(\r?\n)?$/.exec(source)[1] || ''; return { code: upgraded.trim() + eof_newline, diff --git a/test/v3/samples/helper-non-function/output.html b/test/v3/samples/helper-non-function/output.html index ca14158..0c22bd6 100644 --- a/test/v3/samples/helper-non-function/output.html +++ b/test/v3/samples/helper-non-function/output.html @@ -8,4 +8,4 @@ }; -{Math.min(x, 5)} \ No newline at end of file +{Math.min(x, 5)} diff --git a/test/v3/samples/store/output.html b/test/v3/samples/store/output.html index 2ca57f9..905d3c7 100644 --- a/test/v3/samples/store/output.html +++ b/test/v3/samples/store/output.html @@ -13,4 +13,4 @@ */ -

Hello {$name}!

\ No newline at end of file +

Hello {$name}!

From fcb89856971cd8067f3256f1f1aac164089b7957 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Nov 2018 10:25:39 -0500 Subject: [PATCH 39/52] rewrite fire to dispatch --- src/v3/handlers/on_directive.js | 6 ++++++ src/v3/handlers/oncreate_ondestroy.js | 4 ++-- src/v3/handlers/onstate_onupdate.js | 4 ++-- src/v3/handlers/shared/rewrite_this.js | 3 ++- src/v3/index.js | 14 ++++++++++---- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/v3/handlers/on_directive.js b/src/v3/handlers/on_directive.js index 662008a..7e68647 100644 --- a/src/v3/handlers/on_directive.js +++ b/src/v3/handlers/on_directive.js @@ -9,6 +9,12 @@ export default function handle_on_directive(node, info, parent) { const { code } = info; const { arguments: args, callee, start, end } = node.expression; + if (callee.name === 'fire') { + info.uses_dispatch = true; + info.imported_functions.add('createEventDispatcher'); + code.overwrite(callee.start, callee.end, 'dispatch'); + } + if (args.length === 0 || (args.length === 1 && args[0].name === 'event')) { code.remove(callee.end, end); } else { diff --git a/src/v3/handlers/oncreate_ondestroy.js b/src/v3/handlers/oncreate_ondestroy.js index 4ad900b..2b2b924 100644 --- a/src/v3/handlers/oncreate_ondestroy.js +++ b/src/v3/handlers/oncreate_ondestroy.js @@ -1,9 +1,9 @@ import rewrite_this from './shared/rewrite_this.js'; export default function handle_oncreate_ondestroy(node, info, name) { - const { code, blocks, lifecycle_functions, indent_regex } = info; + const { code, blocks, imported_functions, indent_regex } = info; - lifecycle_functions.add(name); + imported_functions.add(name); if (node.type === 'FunctionExpression') { rewrite_this(node.body, info); diff --git a/src/v3/handlers/onstate_onupdate.js b/src/v3/handlers/onstate_onupdate.js index c225bf5..4636c37 100644 --- a/src/v3/handlers/onstate_onupdate.js +++ b/src/v3/handlers/onstate_onupdate.js @@ -1,9 +1,9 @@ import rewrite_this from './shared/rewrite_this.js'; export default function handle_onstate_onupdate(node, info, name) { - const { code, blocks, lifecycle_functions, indent, indent_regex } = info; + const { code, blocks, imported_functions, indent, indent_regex } = info; - lifecycle_functions.add(name); + imported_functions.add(name); if (node.type === 'FunctionExpression') { rewrite_this(node.body, info); diff --git a/src/v3/handlers/shared/rewrite_this.js b/src/v3/handlers/shared/rewrite_this.js index e98b9e2..6b77445 100644 --- a/src/v3/handlers/shared/rewrite_this.js +++ b/src/v3/handlers/shared/rewrite_this.js @@ -41,8 +41,9 @@ export default function rewrite_this(node, info, is_event_handler, replacement = switch (child.property.name) { case 'fire': info.uses_dispatch = true; + info.imported_functions.add('createEventDispatcher'); code.overwrite(child.start, child.end, `dispatch`); - break; + return this.skip(); default: code.overwrite(child.object.start, child.object.end, replacement); diff --git a/src/v3/index.js b/src/v3/index.js index 8e81635..989c8c5 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -76,7 +76,7 @@ export function upgradeTemplate(source) { const info = { source, code, - lifecycle_functions: new Set(), + imported_functions: new Set(), props, blocks: [], shared_blocks: [], @@ -222,7 +222,13 @@ export function upgradeTemplate(source) { prop_declarations.push(`export let ${key}${value === 'undefined' ? '' : ` = ${value}`};`); } - if (prop_declarations.length > 0) info.blocks.unshift(prop_declarations.join(`\n${indent}`)); + if (prop_declarations.length > 0) { + info.blocks.unshift(prop_declarations.join(`\n${indent}`)); + } + + if (info.uses_dispatch) { + info.blocks.unshift(`const dispatch = createEventDispatcher();`); + } code.overwrite(default_export.start, default_export.end, info.blocks.join(`\n\n${indent}`)); } @@ -262,8 +268,8 @@ export function upgradeTemplate(source) { code.move(result.ast.js.start, result.ast.js.end, 0); } - if (info.lifecycle_functions.size > 0) { - const specifiers = Array.from(info.lifecycle_functions).sort().join(', '); + if (info.imported_functions.size > 0) { + const specifiers = Array.from(info.imported_functions).sort().join(', '); info.imports.unshift(`import { ${specifiers} } from 'svelte';`); } From f99223348138d792cf00479f9ea3cd2af5a0c341 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Nov 2018 11:37:38 -0500 Subject: [PATCH 40/52] rewrite fire as dispatch --- src/v3/handlers/on_directive.js | 1 - src/v3/handlers/shared/rewrite_this.js | 1 - src/v3/index.js | 325 +++++++++---------- test/v3/samples/fire-from-event/input.html | 1 + test/v3/samples/fire-from-event/output.html | 7 + test/v3/samples/fire-from-method/input.html | 11 + test/v3/samples/fire-from-method/output.html | 13 + 7 files changed, 193 insertions(+), 166 deletions(-) create mode 100644 test/v3/samples/fire-from-event/input.html create mode 100644 test/v3/samples/fire-from-event/output.html create mode 100644 test/v3/samples/fire-from-method/input.html create mode 100644 test/v3/samples/fire-from-method/output.html diff --git a/src/v3/handlers/on_directive.js b/src/v3/handlers/on_directive.js index 7e68647..efcba58 100644 --- a/src/v3/handlers/on_directive.js +++ b/src/v3/handlers/on_directive.js @@ -11,7 +11,6 @@ export default function handle_on_directive(node, info, parent) { if (callee.name === 'fire') { info.uses_dispatch = true; - info.imported_functions.add('createEventDispatcher'); code.overwrite(callee.start, callee.end, 'dispatch'); } diff --git a/src/v3/handlers/shared/rewrite_this.js b/src/v3/handlers/shared/rewrite_this.js index 6b77445..7da3648 100644 --- a/src/v3/handlers/shared/rewrite_this.js +++ b/src/v3/handlers/shared/rewrite_this.js @@ -41,7 +41,6 @@ export default function rewrite_this(node, info, is_event_handler, replacement = switch (child.property.name) { case 'fire': info.uses_dispatch = true; - info.imported_functions.add('createEventDispatcher'); code.overwrite(child.start, child.end, `dispatch`); return this.skip(); diff --git a/src/v3/index.js b/src/v3/index.js index 989c8c5..a8b077d 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -104,197 +104,128 @@ export function upgradeTemplate(source) { } }; - if (result.ast.js) { - const { body } = result.ast.js.content; - - const default_export = body.find(node => node.type === 'ExportDefaultDeclaration'); - find_declarations(body, info.declarations); + const body = result.ast.js && result.ast.js.content.body; + const default_export = body && body.find(node => node.type === 'ExportDefaultDeclaration'); - if (default_export) { - // TODO set up indentExclusionRanges + if (body) find_declarations(body, info.declarations); - default_export.declaration.properties.forEach(prop => { - if (prop.key.name === 'methods') { - prop.value.properties.forEach(node => { - info.methods.add(node.key.name); - }); - } - - if (prop.key.name === 'computed') { - prop.value.properties.forEach(node => { - info.computed.add(node.key.name); - }); - } + if (default_export) { + // TODO set up indentExclusionRanges - if (prop.key.name === 'helpers') { - prop.value.properties.forEach(node => { - info.helpers.add(node.key.name); - }); - } - }); - - default_export.declaration.properties.forEach(prop => { - switch (prop.key.name) { - case 'actions': - handle_registrants(prop.value.properties, info, 'action') - break; - - case 'animations': - handle_registrants(prop.value.properties, info, 'animation') - break; - - case 'components': - handle_components(prop.value, info); - break; - - case 'computed': - handle_computed(prop.value, info); - break; - - case 'data': - handle_data(prop.value, info); - break; - - case 'events': - handle_registrants(prop.value.properties, info, 'event'); - break; - - case 'helpers': - handle_registrants(prop.value.properties, info, 'helper'); - break; - - case 'immutable': - immutable = prop.value.value; - break; - - case 'methods': - handle_methods(prop.value, info); - break; - - case 'oncreate': case 'onrender': - handle_oncreate_ondestroy(prop.value, info, 'onmount'); - break; - - case 'ondestroy': case 'onteardown': - handle_oncreate_ondestroy(prop.value, info, 'ondestroy'); - break; - - case 'onstate': - handle_onstate_onupdate(prop.value, info, 'onprops'); - break; - - case 'onupdate': - handle_onstate_onupdate(prop.value, info, 'onupdate'); - break; - - case 'preload': - handle_preload(prop.value, info); - break; - - case 'setup': - handle_setup(prop, info); - break; - - case 'store': - handle_store(prop, info); - break; - - case 'tag': - tag = prop.value.value; - break; - - case 'transitions': - handle_registrants(prop.value.properties, info, 'transition') - break; - - case 'namespace': - namespace = prop.value.value; - break; - - default: - throw new Error(`Not implemented: ${prop.key.name}`); - } - }); - - let prop_declarations = []; - for (const [key, value] of props) { - if (key === value) continue; - prop_declarations.push(`export let ${key}${value === 'undefined' ? '' : ` = ${value}`};`); + default_export.declaration.properties.forEach(prop => { + if (prop.key.name === 'methods') { + prop.value.properties.forEach(node => { + info.methods.add(node.key.name); + }); } - if (prop_declarations.length > 0) { - info.blocks.unshift(prop_declarations.join(`\n${indent}`)); + if (prop.key.name === 'computed') { + prop.value.properties.forEach(node => { + info.computed.add(node.key.name); + }); } - if (info.uses_dispatch) { - info.blocks.unshift(`const dispatch = createEventDispatcher();`); + if (prop.key.name === 'helpers') { + prop.value.properties.forEach(node => { + info.helpers.add(node.key.name); + }); } + }); - code.overwrite(default_export.start, default_export.end, info.blocks.join(`\n\n${indent}`)); - } + default_export.declaration.properties.forEach(prop => { + switch (prop.key.name) { + case 'actions': + handle_registrants(prop.value.properties, info, 'action') + break; - code.appendLeft(result.ast.js.end, '\n\n'); + case 'animations': + handle_registrants(prop.value.properties, info, 'animation') + break; - const needs_script = ( - info.blocks.length > 0 || - !!body.find(node => node !== default_export) - ); + case 'components': + handle_components(prop.value, info); + break; - if (needs_script) { - if (info.blocks.length === 0 && default_export) { - const index = body.indexOf(default_export); + case 'computed': + handle_computed(prop.value, info); + break; - let a = default_export.start; - let b = default_export.end; + case 'data': + handle_data(prop.value, info); + break; - // need to remove whitespace around the default export - if (index === 0) { - throw new Error(`TODO remove default export from start`); - } else if (index === body.length - 1) { - while (/\s/.test(source[a - 1])) a -= 1; - } else { - throw new Error(`TODO remove default export from middle`); - } + case 'events': + handle_registrants(prop.value.properties, info, 'event'); + break; - code.remove(a, b); - } + case 'helpers': + handle_registrants(prop.value.properties, info, 'helper'); + break; - const { start } = body[0]; - const { end } = body[body.length - 1]; + case 'immutable': + immutable = prop.value.value; + break; - script_sections.push(code.slice(start, end)); + case 'methods': + handle_methods(prop.value, info); + break; - if (result.ast.js.start !== 0) { - code.move(result.ast.js.start, result.ast.js.end, 0); - } + case 'oncreate': case 'onrender': + handle_oncreate_ondestroy(prop.value, info, 'onmount'); + break; - if (info.imported_functions.size > 0) { - const specifiers = Array.from(info.imported_functions).sort().join(', '); - info.imports.unshift(`import { ${specifiers} } from 'svelte';`); - } + case 'ondestroy': case 'onteardown': + handle_oncreate_ondestroy(prop.value, info, 'ondestroy'); + break; - if (info.uses_this) { - const this_props = []; + case 'onstate': + handle_onstate_onupdate(prop.value, info, 'onprops'); + break; - if (info.uses_this_properties.has('get')) { - const props = Array.from(info.props.keys()); - this_props.push(`get: () => ({ ${props.join(', ')} })`); - } + case 'onupdate': + handle_onstate_onupdate(prop.value, info, 'onupdate'); + break; - const rhs = this_props.length - ? `{\n${indent}${indent}${this_props.join(`\n${indent}${indent}`)}\n${indent}}` - : `{}`; + case 'preload': + handle_preload(prop.value, info); + break; - script_sections.unshift(`// [svelte-upgrade suggestion]\n${indent}// manually refactor all references to __this\n${indent}const __this = ${rhs};`); - info.manual_edits_suggested = true; - } + case 'setup': + handle_setup(prop, info); + break; + + case 'store': + handle_store(prop, info); + break; + + case 'tag': + tag = prop.value.value; + break; - if (info.imports.length) { - script_sections.unshift(`${info.imports.join(`\n${indent}`)}`); + case 'transitions': + handle_registrants(prop.value.properties, info, 'transition') + break; + + case 'namespace': + namespace = prop.value.value; + break; + + default: + throw new Error(`Not implemented: ${prop.key.name}`); } + }); + + let prop_declarations = []; + for (const [key, value] of props) { + if (key === value) continue; + prop_declarations.push(`export let ${key}${value === 'undefined' ? '' : ` = ${value}`};`); } - code.remove(result.ast.js.start, result.ast.js.end); + if (prop_declarations.length > 0) { + info.blocks.unshift(prop_declarations.join(`\n${indent}`)); + } + + code.overwrite(default_export.start, default_export.end, info.blocks.join(`\n\n${indent}`)); } let scope = new Set(); @@ -351,6 +282,72 @@ export function upgradeTemplate(source) { } }); + const needs_script = ( + info.uses_dispatch || + info.blocks.length > 0 || + (body && !!body.find(node => node !== default_export)) + ); + + if (needs_script) { + if (info.blocks.length === 0 && default_export) { + const index = body.indexOf(default_export); + + let a = default_export.start; + let b = default_export.end; + + // need to remove whitespace around the default export + if (index === 0) { + throw new Error(`TODO remove default export from start`); + } else if (index === body.length - 1) { + while (/\s/.test(source[a - 1])) a -= 1; + } else { + throw new Error(`TODO remove default export from middle`); + } + + code.remove(a, b); + } + + if (info.uses_dispatch) { + info.imported_functions.add('createEventDispatcher'); + script_sections.push(`const dispatch = createEventDispatcher();`); + } + + if (body) { + const { start } = body[0]; + const { end } = body[body.length - 1]; + script_sections.push(code.slice(start, end)); + } + + if (info.imported_functions.size > 0) { + const specifiers = Array.from(info.imported_functions).sort().join(', '); + info.imports.unshift(`import { ${specifiers} } from 'svelte';`); + } + + if (info.uses_this) { + const this_props = []; + + if (info.uses_this_properties.has('get')) { + const props = Array.from(info.props.keys()); + this_props.push(`get: () => ({ ${props.join(', ')} })`); + } + + const rhs = this_props.length + ? `{\n${indent}${indent}${this_props.join(`\n${indent}${indent}`)}\n${indent}}` + : `{}`; + + script_sections.unshift(`// [svelte-upgrade suggestion]\n${indent}// manually refactor all references to __this\n${indent}const __this = ${rhs};`); + info.manual_edits_suggested = true; + } + + if (info.imports.length) { + script_sections.unshift(`${info.imports.join(`\n${indent}`)}`); + } + } + + if (result.ast.js) { + code.remove(result.ast.js.start, result.ast.js.end); + } + let upgraded = code.toString().trim(); if (script_sections.length > 0) { diff --git a/test/v3/samples/fire-from-event/input.html b/test/v3/samples/fire-from-event/input.html new file mode 100644 index 0000000..3dfa339 --- /dev/null +++ b/test/v3/samples/fire-from-event/input.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/v3/samples/fire-from-event/output.html b/test/v3/samples/fire-from-event/output.html new file mode 100644 index 0000000..00e7543 --- /dev/null +++ b/test/v3/samples/fire-from-event/output.html @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/fire-from-method/input.html b/test/v3/samples/fire-from-method/input.html new file mode 100644 index 0000000..e4f1449 --- /dev/null +++ b/test/v3/samples/fire-from-method/input.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/fire-from-method/output.html b/test/v3/samples/fire-from-method/output.html new file mode 100644 index 0000000..ff1e4f6 --- /dev/null +++ b/test/v3/samples/fire-from-method/output.html @@ -0,0 +1,13 @@ + + + \ No newline at end of file From 2cc6f6707433e403e0b9a6a128a402a47409afbe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 10 Nov 2018 12:08:59 -0500 Subject: [PATCH 41/52] bail out of computed props with default args --- src/v3/handlers/computed.js | 4 ++++ test/v3/samples/computed-default/error.js | 11 +++++++++++ test/v3/samples/computed-default/input.html | 9 +++++++++ 3 files changed, 24 insertions(+) create mode 100644 test/v3/samples/computed-default/error.js create mode 100644 test/v3/samples/computed-default/input.html diff --git a/src/v3/handlers/computed.js b/src/v3/handlers/computed.js index 1537008..41228db 100644 --- a/src/v3/handlers/computed.js +++ b/src/v3/handlers/computed.js @@ -23,6 +23,10 @@ export default function handle_computed(node, info) { statements = computed.value.params[0].properties .filter(param => param.value.type !== 'Identifier') .map(param => { + if (param.value.type === 'AssignmentPattern') { + info.error(`svelte-upgrade cannot currently process default computed property arguments`, param.start); + } + const { name } = param.key; const lhs = code.slice(param.value.start, param.value.end); const rhs = info.computed.has(name) ? `${name}()` : name; diff --git a/test/v3/samples/computed-default/error.js b/test/v3/samples/computed-default/error.js new file mode 100644 index 0000000..24a2786 --- /dev/null +++ b/test/v3/samples/computed-default/error.js @@ -0,0 +1,11 @@ +module.exports = { + message: "svelte-upgrade cannot currently process default computed property arguments", + pos: 72, + frame: ` + 4: export default { + 5: computed: { + 6: b: ({ a = 1 }) => a * 2 + ^ + 7: } + 8: };` +}; \ No newline at end of file diff --git a/test/v3/samples/computed-default/input.html b/test/v3/samples/computed-default/input.html new file mode 100644 index 0000000..fcc3549 --- /dev/null +++ b/test/v3/samples/computed-default/input.html @@ -0,0 +1,9 @@ +

{a} * 2 = {b}

+ + \ No newline at end of file From 75b973a356bf9ccae404f82aa9abb0bc8aa71309 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 12 Nov 2018 14:24:39 -0500 Subject: [PATCH 42/52] various events and refs stuff --- src/v3/handlers/on_directive.js | 2 +- src/v3/handlers/shared/rewrite_this.js | 32 +++++++++++++++---- src/v3/index.js | 15 +++++++++ .../input.html | 3 ++ .../output.html | 3 ++ .../v3/samples/fire-from-method/observable.js | 0 test/v3/samples/fire-from-method/user.js | 0 .../ref-in-function-destructured/input.html | 10 ++++++ .../ref-in-function-destructured/output.html | 15 +++++++++ test/v3/samples/ref-in-function/input.html | 9 ++++++ test/v3/samples/ref-in-function/output.html | 15 +++++++++ test/v3/samples/ref/input.html | 1 + test/v3/samples/ref/output.html | 1 + 13 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 test/v3/samples/event-handler-event-arg-context/input.html create mode 100644 test/v3/samples/event-handler-event-arg-context/output.html create mode 100644 test/v3/samples/fire-from-method/observable.js create mode 100644 test/v3/samples/fire-from-method/user.js create mode 100644 test/v3/samples/ref-in-function-destructured/input.html create mode 100644 test/v3/samples/ref-in-function-destructured/output.html create mode 100644 test/v3/samples/ref-in-function/input.html create mode 100644 test/v3/samples/ref-in-function/output.html create mode 100644 test/v3/samples/ref/input.html create mode 100644 test/v3/samples/ref/output.html diff --git a/src/v3/handlers/on_directive.js b/src/v3/handlers/on_directive.js index efcba58..78c1200 100644 --- a/src/v3/handlers/on_directive.js +++ b/src/v3/handlers/on_directive.js @@ -14,7 +14,7 @@ export default function handle_on_directive(node, info, parent) { code.overwrite(callee.start, callee.end, 'dispatch'); } - if (args.length === 0 || (args.length === 1 && args[0].name === 'event')) { + if (callee.type === 'Identifier' && (args.length === 0 || (args.length === 1 && args[0].name === 'event'))) { code.remove(callee.end, end); } else { const uses_event = find_event(node.expression); diff --git a/src/v3/handlers/shared/rewrite_this.js b/src/v3/handlers/shared/rewrite_this.js index 7da3648..52074d4 100644 --- a/src/v3/handlers/shared/rewrite_this.js +++ b/src/v3/handlers/shared/rewrite_this.js @@ -13,15 +13,21 @@ export default function rewrite_this(node, info, is_event_handler, replacement = } if (child.type === 'VariableDeclarator') { - if (child.init && child.init.type === 'CallExpression') { - if (is_method(child.init.callee, 'get')) { - rewrite_get(child, parent, info); - return this.skip(); + if (child.init) { + if (child.init.type === 'CallExpression') { + if (is_method(child.init.callee, 'get')) { + rewrite_get(child, parent, info); + return this.skip(); + } + } + + if (is_this_property(child.init) && child.init.property.name === 'refs') { + throw new Error(`TODO handle 'const { ... } = this.refs'`); } } } - if (child.type === 'CallExpression') { + else if (child.type === 'CallExpression') { if (is_method(child.callee, 'set', is_event_handler)) { rewrite_set(child, info); return this.skip(); @@ -30,7 +36,7 @@ export default function rewrite_this(node, info, is_event_handler, replacement = // TODO optimise get } - if (child.type === 'MemberExpression' && child.object.type === 'ThisExpression') { + else if (is_this_property(child)) { if (!child.property.computed) { if (methods.has(child.property.name)) { code.remove(child.object.start, child.property.start); @@ -53,6 +59,17 @@ export default function rewrite_this(node, info, is_event_handler, replacement = } } } + + else if (child.type === 'MemberExpression') { + if (is_this_property(child.object) && child.object.property.name === 'refs') { + if (child.property.computed) { + throw new Error(`TODO handle standalone this.refs`); + } + + code.remove(child.object.start, child.property.start); + this.skip(); + } + } } }); } @@ -60,7 +77,8 @@ export default function rewrite_this(node, info, is_event_handler, replacement = function is_this_property(node) { return ( node.type === 'MemberExpression' && - node.object.type === 'ThisExpression' + node.object.type === 'ThisExpression' && + !node.property.computed ); } diff --git a/src/v3/index.js b/src/v3/index.js index a8b077d..b7300f9 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -16,6 +16,7 @@ import { find_declarations, get_code_frame } from './utils.js'; import { extract_names } from './scopes.js'; import rewrite_computed from './rewrite_computed.js'; import handle_onstate_onupdate from './handlers/onstate_onupdate.js'; +import add_declaration from './handlers/shared/add_declaration.js'; // We need to tell estree-walker that it should always // look for an `else` block, otherwise it might get @@ -113,6 +114,8 @@ export function upgradeTemplate(source) { // TODO set up indentExclusionRanges default_export.declaration.properties.forEach(prop => { + // TODO could these conflict with props? + if (prop.key.name === 'methods') { prop.value.properties.forEach(node => { info.methods.add(node.key.name); @@ -231,6 +234,8 @@ export function upgradeTemplate(source) { let scope = new Set(); const scopes = [scope]; + const refs = new Set(); + walk(result.ast.html, { enter(node, parent) { switch (node.type) { @@ -271,6 +276,12 @@ export function upgradeTemplate(source) { // TODO also need to do this for expressions in blocks, attributes and directives rewrite_computed(node, info, scope); break; + + case 'Ref': + if (!refs.has(node.name)) { + refs.add(node.name); + add_declaration(node, info); + } } }, @@ -312,6 +323,10 @@ export function upgradeTemplate(source) { script_sections.push(`const dispatch = createEventDispatcher();`); } + if (refs.size > 0) { + script_sections.push(Array.from(refs).map(name => `export let ${name};`).join(`\n${indent}`)); + } + if (body) { const { start } = body[0]; const { end } = body[body.length - 1]; diff --git a/test/v3/samples/event-handler-event-arg-context/input.html b/test/v3/samples/event-handler-event-arg-context/input.html new file mode 100644 index 0000000..47d4284 --- /dev/null +++ b/test/v3/samples/event-handler-event-arg-context/input.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/v3/samples/event-handler-event-arg-context/output.html b/test/v3/samples/event-handler-event-arg-context/output.html new file mode 100644 index 0000000..14baaf1 --- /dev/null +++ b/test/v3/samples/event-handler-event-arg-context/output.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test/v3/samples/fire-from-method/observable.js b/test/v3/samples/fire-from-method/observable.js new file mode 100644 index 0000000..e69de29 diff --git a/test/v3/samples/fire-from-method/user.js b/test/v3/samples/fire-from-method/user.js new file mode 100644 index 0000000..e69de29 diff --git a/test/v3/samples/ref-in-function-destructured/input.html b/test/v3/samples/ref-in-function-destructured/input.html new file mode 100644 index 0000000..6504915 --- /dev/null +++ b/test/v3/samples/ref-in-function-destructured/input.html @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/ref-in-function-destructured/output.html b/test/v3/samples/ref-in-function-destructured/output.html new file mode 100644 index 0000000..935e1e9 --- /dev/null +++ b/test/v3/samples/ref-in-function-destructured/output.html @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/ref-in-function/input.html b/test/v3/samples/ref-in-function/input.html new file mode 100644 index 0000000..6225dcc --- /dev/null +++ b/test/v3/samples/ref-in-function/input.html @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/ref-in-function/output.html b/test/v3/samples/ref-in-function/output.html new file mode 100644 index 0000000..935e1e9 --- /dev/null +++ b/test/v3/samples/ref-in-function/output.html @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/ref/input.html b/test/v3/samples/ref/input.html new file mode 100644 index 0000000..6c3cb03 --- /dev/null +++ b/test/v3/samples/ref/input.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/v3/samples/ref/output.html b/test/v3/samples/ref/output.html new file mode 100644 index 0000000..6c3cb03 --- /dev/null +++ b/test/v3/samples/ref/output.html @@ -0,0 +1 @@ + \ No newline at end of file From 618a593224c808f87a1c96ea6547f38169dfbb66 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 12 Nov 2018 16:25:14 -0500 Subject: [PATCH 43/52] various fixes --- src/cli.js | 2 +- src/v3/handlers/components.js | 2 +- src/v3/handlers/data.js | 7 +++++-- src/v3/handlers/methods.js | 4 ++-- src/v3/handlers/shared/handle_registrants.js | 4 ++-- .../{use_directive.js => wrap_with_curlies.js} | 2 +- src/v3/index.js | 5 +++-- test/v3/samples/data-indentation-arrow/input.html | 15 +++++++++++++++ .../v3/samples/data-indentation-arrow/output.html | 11 +++++++++++ test/v3/samples/transition/input.html | 9 +++++++++ test/v3/samples/transition/output.html | 5 +++++ 11 files changed, 55 insertions(+), 11 deletions(-) rename src/v3/handlers/{use_directive.js => wrap_with_curlies.js} (71%) create mode 100644 test/v3/samples/data-indentation-arrow/input.html create mode 100644 test/v3/samples/data-indentation-arrow/output.html create mode 100644 test/v3/samples/transition/input.html create mode 100644 test/v3/samples/transition/output.html diff --git a/src/cli.js b/src/cli.js index 9cf087b..4b19f00 100644 --- a/src/cli.js +++ b/src/cli.js @@ -141,7 +141,7 @@ function get_tasks(items, in_dir, out_dir, arr = []) { let message = `Wrote ${count(tasks.length)}`; if (unchanged_count > 0) { - message += `. ${count(unchanged_count)} components required no changes`; + message += `. ${count(unchanged_count)} required no changes`; } console.error(c.cyan(message)); diff --git a/src/v3/handlers/components.js b/src/v3/handlers/components.js index 1f03e2d..971bfff 100644 --- a/src/v3/handlers/components.js +++ b/src/v3/handlers/components.js @@ -13,6 +13,6 @@ export default function handle_components(node, info) { }); if (statements.length > 0) { - blocks.push(statements.join('\n')); + blocks.push(statements.join(`\n${info.indent}`)); } } \ No newline at end of file diff --git a/src/v3/handlers/data.js b/src/v3/handlers/data.js index 4414cc2..2c4193a 100644 --- a/src/v3/handlers/data.js +++ b/src/v3/handlers/data.js @@ -48,11 +48,14 @@ export default function handle_data(node, info) { } returned.properties.forEach(prop => { - const body = code.original.slice(prop.value.start, prop.value.end) - .replace(indent_regex, '') + let body = code.original.slice(prop.value.start, prop.value.end) .replace(indent_regex, '') .replace(indent_regex, ''); + if (node.type === 'FunctionExpression' || node.body.type === 'BlockStatement') { + body = body.replace(indent_regex, '') + } + props.set(prop.key.name, body); }); } \ No newline at end of file diff --git a/src/v3/handlers/methods.js b/src/v3/handlers/methods.js index 2de481d..2749717 100644 --- a/src/v3/handlers/methods.js +++ b/src/v3/handlers/methods.js @@ -2,7 +2,7 @@ import rewrite_this from './shared/rewrite_this.js'; import add_declaration from './shared/add_declaration.js'; export default function handle_methods(node, info) { - const { blocks, code, error } = info; + const { blocks, code, error, indent } = info; const statements = []; let suggested = false; @@ -37,7 +37,7 @@ export default function handle_methods(node, info) { }); if (statements.length > 0) { - blocks.push(`${statements.join('\n')}`); + blocks.push(`${statements.join(`\n${indent}`)}`); } info.manual_edits_suggested = true; diff --git a/src/v3/handlers/shared/handle_registrants.js b/src/v3/handlers/shared/handle_registrants.js index 1ace38d..e89e018 100644 --- a/src/v3/handlers/shared/handle_registrants.js +++ b/src/v3/handlers/shared/handle_registrants.js @@ -3,7 +3,7 @@ import alias_registration from './alias_registration.js'; import add_declaration from './add_declaration.js'; export default function handle_registrants(registrants, info, type) { - const { blocks, code, error } = info; + const { blocks, code, indent } = info; const statements = []; registrants.forEach(registrant => { @@ -36,6 +36,6 @@ export default function handle_registrants(registrants, info, type) { }); if (statements.length > 0) { - blocks.push(`${statements.join('\n')}`); + blocks.push(`${statements.join(`\n${indent}`)}`); } } \ No newline at end of file diff --git a/src/v3/handlers/use_directive.js b/src/v3/handlers/wrap_with_curlies.js similarity index 71% rename from src/v3/handlers/use_directive.js rename to src/v3/handlers/wrap_with_curlies.js index daf2d8e..62b4bbc 100644 --- a/src/v3/handlers/use_directive.js +++ b/src/v3/handlers/wrap_with_curlies.js @@ -1,4 +1,4 @@ -export default function handle_use_directive(node, info) { +export default function wrap_with_curlies(node, info) { if (!node.expression) return; const { code } = info; diff --git a/src/v3/index.js b/src/v3/index.js index b7300f9..59dc891 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -7,7 +7,7 @@ import handle_data from './handlers/data.js'; import handle_methods from './handlers/methods.js'; import handle_oncreate_ondestroy from './handlers/oncreate_ondestroy.js'; import handle_on_directive from './handlers/on_directive'; -import handle_use_directive from './handlers/use_directive'; +import wrap_with_curlies from './handlers/wrap_with_curlies'; import handle_registrants from './handlers/shared/handle_registrants.js'; import handle_preload from './handlers/preload.js'; import handle_setup from './handlers/setup.js'; @@ -268,7 +268,8 @@ export function upgradeTemplate(source) { break; case 'Action': - handle_use_directive(node, info, parent); + case 'Transition': + wrap_with_curlies(node, info, parent); break; case 'MustacheTag': diff --git a/test/v3/samples/data-indentation-arrow/input.html b/test/v3/samples/data-indentation-arrow/input.html new file mode 100644 index 0000000..31f3f1e --- /dev/null +++ b/test/v3/samples/data-indentation-arrow/input.html @@ -0,0 +1,15 @@ +{#each items as item} +

{item}

+{/each} + + \ No newline at end of file diff --git a/test/v3/samples/data-indentation-arrow/output.html b/test/v3/samples/data-indentation-arrow/output.html new file mode 100644 index 0000000..afa0ba5 --- /dev/null +++ b/test/v3/samples/data-indentation-arrow/output.html @@ -0,0 +1,11 @@ + + +{#each items as item} +

{item}

+{/each} \ No newline at end of file diff --git a/test/v3/samples/transition/input.html b/test/v3/samples/transition/input.html new file mode 100644 index 0000000..a181643 --- /dev/null +++ b/test/v3/samples/transition/input.html @@ -0,0 +1,9 @@ +
+ + \ No newline at end of file diff --git a/test/v3/samples/transition/output.html b/test/v3/samples/transition/output.html new file mode 100644 index 0000000..d4bd7bb --- /dev/null +++ b/test/v3/samples/transition/output.html @@ -0,0 +1,5 @@ + + +
\ No newline at end of file From 44a4225aa2a20225404b08317a694e85fecf17f0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 26 Nov 2018 23:01:35 -0500 Subject: [PATCH 44/52] bindings and class directives --- package.json | 1 - src/cli.js | 7 +- src/v3/index.js | 2 + test/v3/samples/binding/input.html | 1 + test/v3/samples/binding/output.html | 1 + yarn.lock | 111 ++++++---------------------- 6 files changed, 30 insertions(+), 93 deletions(-) create mode 100644 test/v3/samples/binding/input.html create mode 100644 test/v3/samples/binding/output.html diff --git a/package.json b/package.json index 84f2e32..46ffa06 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "license": "LIL", "dependencies": { "estree-walker": "^0.5.2", - "glob": "^7.1.3", "is-reference": "^1.1.0", "kleur": "^2.0.2", "locate-character": "^2.0.5", diff --git a/src/cli.js b/src/cli.js index 4b19f00..f36e58e 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,7 +1,6 @@ import fs from 'fs'; import path from 'path'; import sade from 'sade'; -import glob from 'glob'; import prompts from 'prompts'; import c from 'kleur'; import * as pkg from '../package.json'; @@ -32,8 +31,8 @@ function get_tasks(items, in_dir, out_dir, arr = []) { if (stats.isDirectory()) { get_tasks( fs.readdirSync(item).map(file => path.resolve(item, file)), - path.join(in_dir, file), - path.join(out_dir, file), + path.join(in_dir, item), + path.join(out_dir, item), arr ); } else { @@ -76,9 +75,11 @@ function get_tasks(items, in_dir, out_dir, arr = []) { } try { + console.log(1); const upgrade = v === 2 ? await import('./v2/index.js') : await import('./v3/index.js'); + console.log(2); if (!opts.force) { const existing = tasks diff --git a/src/v3/index.js b/src/v3/index.js index 59dc891..79cbcc9 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -268,6 +268,8 @@ export function upgradeTemplate(source) { break; case 'Action': + case 'Binding': + case 'Class': case 'Transition': wrap_with_curlies(node, info, parent); break; diff --git a/test/v3/samples/binding/input.html b/test/v3/samples/binding/input.html new file mode 100644 index 0000000..8ccb81c --- /dev/null +++ b/test/v3/samples/binding/input.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/v3/samples/binding/output.html b/test/v3/samples/binding/output.html new file mode 100644 index 0000000..1615ccf --- /dev/null +++ b/test/v3/samples/binding/output.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a38489b..0a132c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13,9 +13,9 @@ integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== "@types/node@*": - version "10.12.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.2.tgz#d77f9faa027cadad9c912cd47f4f8b07b0fb0864" - integrity sha512-53ElVDSnZeFUUFIYzI8WLQ25IhWzb6vbddNp8UHlXQyU0ET2RhV5zg0NfubzU7iNMh5bBXb0htCzfvrSVNgzaQ== + version "10.12.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.10.tgz#4fa76e6598b7de3f0cb6ec3abacc4f59e5b3a2ce" + integrity sha512-8xZEYckCbUVgK8Eg7lf5Iy4COKJ5uXlnIOnePN0WUwSQggy9tolM+tDJf7wMOnT/JT/W9xDYIaYggt3mRV2O5w== arr-diff@^2.0.0: version "2.0.0" @@ -34,19 +34,6 @@ array-unique@^0.2.1: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -61,11 +48,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - esm@^3.0.84: version "3.0.84" resolved "https://registry.yarnpkg.com/esm/-/esm-3.0.84.tgz#bb108989f4673b32d4f62406869c28eed3815a63" @@ -125,11 +107,6 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -145,31 +122,6 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -257,11 +209,16 @@ kind-of@^6.0.0: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== -kleur@^2.0.1, kleur@^2.0.2: +kleur@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +kleur@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.0.tgz#ae185c2e0641ffb2f8e3d182df13239df9d8d3b2" + integrity sha512-acHc4xKlqTNuAGmbvtd3KuNi1bnlHsdPg6Os1P5s/Ii/6g8MY3caVPDp4md04/twbh4wwPvdpnol1bc9zFsI3w== + locate-character@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-2.0.5.tgz#f2d2614d49820ecb3c92d80d193b8db755f74c0f" @@ -298,13 +255,6 @@ micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - mri@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.1.tgz#85aa26d3daeeeedf80dc5984af95cc5ca5cad9f1" @@ -325,13 +275,6 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - pad-right@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774" @@ -349,22 +292,17 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= prompts@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-1.2.0.tgz#598f7722032fb6c399beb24533129d00604c7007" - integrity sha512-g+I6Cer6EefDTawQhGHpdX98nhD7KQrRqyRgKCb+Sc+GG4P64EWRe5DZE402ZNkwrItf97Asf0L1z0g3waOgAA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-1.2.1.tgz#7fd4116a458d6a62761e3ccb1432d7bbd8b2cb29" + integrity sha512-GE33SMMVO1ISfnq3i6cE+WYK/tLxRWtZiRkl5vdg0KR0owOCPFOsq8BuFajFbW7b2bMHb8krXaQHOpZyUEuvmA== dependencies: - kleur "^2.0.1" + kleur "^3.0.0" sisteransi "^1.0.0" randomatic@^3.0.0: @@ -414,9 +352,9 @@ rollup-pluginutils@^2.3.1: micromatch "^2.3.11" rollup@^0.67.0: - version "0.67.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.67.0.tgz#16d4f259c55224dded6408e7666b7731500797a3" - integrity sha512-p34buXxArhwv9ieTdHvdhdo65Cbig68s/Z8llbZuiX5e+3zCqnBF02Ck9IH0tECrmvvrJVMws32Ry84hTnS1Tw== + version "0.67.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.67.3.tgz#55475b1b62c43220c3b4bd7edc5846233932f50b" + integrity sha512-TyNQCz97rKuVVbsKUTXfwIjV7UljWyTVd7cTMuE+aqlQ7WJslkYF5QaYGjMLR2BlQtUOO5CAxSVnpQ55iYp5jg== dependencies: "@types/estree" "0.0.39" "@types/node" "*" @@ -448,9 +386,9 @@ source-map@^0.6.0: integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== sourcemap-codec@^1.4.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.3.tgz#0ba615b73ec35112f63c2f2d9e7c3f87282b0e33" - integrity sha512-vFrY/x/NdsD7Yc8mpTJXuao9S8lq08Z/kOITHz6b7YbfI9xL8Spe5EvSQUHOI7SbpY8bRPr0U3kKSsPuqEGSfA== + version "1.4.4" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f" + integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg== "svelte-1@npm:svelte@1": version "1.64.1" @@ -458,16 +396,11 @@ sourcemap-codec@^1.4.1: integrity sha512-RsEAcVYF90Bq5y4Lgg56LyKiuxiyDTU+5TG2GM0ppa50z446de0vQgnA3eQTrgecPdRR89ZIqNqN1cgdAdO7wQ== "svelte-2@npm:svelte@2", svelte@^2.15.2: - version "2.15.2" - resolved "https://registry.yarnpkg.com/svelte/-/svelte-2.15.2.tgz#6aaf4a10307409099ec30dd7c73f746fef32c949" - integrity sha512-l/YAQQ/ArmUTlEdspyLS7viFtKX0ZRKVmYeizAlvFoJ4vKos+7/aBD8xWxQTUd8EPh1JD95DVy7NK3h8+ITlxw== + version "2.15.3" + resolved "https://registry.yarnpkg.com/svelte/-/svelte-2.15.3.tgz#d9ba5b48eae30f5501b3266c563add2399c67b40" + integrity sha512-0bKpXppM/YlPZ8F0mREaUYpE8Te11RvG62ttFr+n1U3G2qdr3cZzuXCnQMcVZukwAUJAjRM55OSH7FLmXnPGRA== tape-modern@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/tape-modern/-/tape-modern-1.1.1.tgz#32189f709740672dc4ca965489208caba983986e" integrity sha512-sb1PNwSlphD2t5z9diUO7dd+r25sfdw94wD3yyi1Dcmu7oJ6wKMGdZ/pB6OZ0jqm0oAEESL4iOHeiCqnHhc1dg== - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= From ff4649f3029453474e15f2dfe48df4676588d3b7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 29 Jan 2019 11:43:53 -0500 Subject: [PATCH 45/52] fix lifecycle names, fix refs, fix bindings --- src/cli.js | 2 -- src/v3/handlers/onstate_onupdate.js | 2 +- src/v3/handlers/wrap_with_curlies.js | 8 +++++--- src/v3/index.js | 11 +++++++---- test/v3/samples/get-complex/output.html | 4 ++-- test/v3/samples/get/output.html | 4 ++-- test/v3/samples/method/output.html | 4 ++-- test/v3/samples/oncreate-arrow/output.html | 4 ++-- test/v3/samples/oncreate-async/output.html | 4 ++-- test/v3/samples/oncreate/output.html | 4 ++-- test/v3/samples/onstate/output.html | 6 +++--- test/v3/samples/onupdate/output.html | 6 +++--- .../samples/ref-in-function-destructured/output.html | 6 +++--- test/v3/samples/ref-in-function/output.html | 6 +++--- test/v3/samples/ref/output.html | 2 +- 15 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/cli.js b/src/cli.js index f36e58e..ffbab42 100644 --- a/src/cli.js +++ b/src/cli.js @@ -75,11 +75,9 @@ function get_tasks(items, in_dir, out_dir, arr = []) { } try { - console.log(1); const upgrade = v === 2 ? await import('./v2/index.js') : await import('./v3/index.js'); - console.log(2); if (!opts.force) { const existing = tasks diff --git a/src/v3/handlers/onstate_onupdate.js b/src/v3/handlers/onstate_onupdate.js index 4636c37..c87f92b 100644 --- a/src/v3/handlers/onstate_onupdate.js +++ b/src/v3/handlers/onstate_onupdate.js @@ -11,7 +11,7 @@ export default function handle_onstate_onupdate(node, info, name) { const body = code.slice(node.body.start, node.body.end) .replace(indent_regex, ''); - blocks.push(`// [svelte-upgrade warning]\n${indent}// onprops and onupdate handlers behave\n${indent}// differently to their v2 counterparts\n${indent}${name}(${node.async ? `async ` : ``}() => ${body});`); + blocks.push(`// [svelte-upgrade warning]\n${indent}// beforeUpdate and afterUpdate handlers behave\n${indent}// differently to their v2 counterparts\n${indent}${name}(${node.async ? `async ` : ``}() => ${body});`); } else { diff --git a/src/v3/handlers/wrap_with_curlies.js b/src/v3/handlers/wrap_with_curlies.js index 62b4bbc..f85f323 100644 --- a/src/v3/handlers/wrap_with_curlies.js +++ b/src/v3/handlers/wrap_with_curlies.js @@ -1,8 +1,10 @@ export default function wrap_with_curlies(node, info) { - if (!node.expression) return; + const expression = node.expression || node.value; + + if (!expression) return; const { code } = info; - code.appendLeft(node.expression.start, '{'); - code.prependRight(node.expression.end, '}'); + code.appendLeft(expression.start, '{'); + code.prependRight(expression.end, '}'); } \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 79cbcc9..92680a9 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -174,19 +174,19 @@ export function upgradeTemplate(source) { break; case 'oncreate': case 'onrender': - handle_oncreate_ondestroy(prop.value, info, 'onmount'); + handle_oncreate_ondestroy(prop.value, info, 'onMount'); break; case 'ondestroy': case 'onteardown': - handle_oncreate_ondestroy(prop.value, info, 'ondestroy'); + handle_oncreate_ondestroy(prop.value, info, 'onDestroy'); break; case 'onstate': - handle_onstate_onupdate(prop.value, info, 'onprops'); + handle_onstate_onupdate(prop.value, info, 'beforeUpdate'); break; case 'onupdate': - handle_onstate_onupdate(prop.value, info, 'onupdate'); + handle_onstate_onupdate(prop.value, info, 'afterUpdate'); break; case 'preload': @@ -285,6 +285,9 @@ export function upgradeTemplate(source) { refs.add(node.name); add_declaration(node, info); } + + code.overwrite(node.start, node.end, `bind:this={${node.name}}`); + break; } }, diff --git a/test/v3/samples/get-complex/output.html b/test/v3/samples/get-complex/output.html index 41bff9d..c3e1639 100644 --- a/test/v3/samples/get-complex/output.html +++ b/test/v3/samples/get-complex/output.html @@ -1,5 +1,5 @@ diff --git a/test/v3/samples/method/output.html b/test/v3/samples/method/output.html index 67511ea..5a2530c 100644 --- a/test/v3/samples/method/output.html +++ b/test/v3/samples/method/output.html @@ -1,7 +1,7 @@ diff --git a/test/v3/samples/oncreate-async/output.html b/test/v3/samples/oncreate-async/output.html index 2de424c..3b52e0a 100644 --- a/test/v3/samples/oncreate-async/output.html +++ b/test/v3/samples/oncreate-async/output.html @@ -1,11 +1,11 @@ diff --git a/test/v3/samples/oncreate/output.html b/test/v3/samples/oncreate/output.html index b156680..4a5a69e 100644 --- a/test/v3/samples/oncreate/output.html +++ b/test/v3/samples/oncreate/output.html @@ -1,11 +1,11 @@ diff --git a/test/v3/samples/onstate/output.html b/test/v3/samples/onstate/output.html index 928beb9..3b62111 100644 --- a/test/v3/samples/onstate/output.html +++ b/test/v3/samples/onstate/output.html @@ -1,10 +1,10 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/v3/samples/ref-in-function/output.html b/test/v3/samples/ref-in-function/output.html index 935e1e9..fa854dc 100644 --- a/test/v3/samples/ref-in-function/output.html +++ b/test/v3/samples/ref-in-function/output.html @@ -1,5 +1,5 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/test/v3/samples/ref/output.html b/test/v3/samples/ref/output.html index 6c3cb03..b997d2e 100644 --- a/test/v3/samples/ref/output.html +++ b/test/v3/samples/ref/output.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 445aa992cd983965f5ebcd0e6135fc86b55f17a5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 29 Jan 2019 13:34:07 -0500 Subject: [PATCH 46/52] handle destructured refs pattern --- src/v3/handlers/shared/rewrite_refs.js | 27 ++++++++++++++++++++++++++ src/v3/handlers/shared/rewrite_this.js | 4 +++- src/v3/index.js | 8 +++++++- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/v3/handlers/shared/rewrite_refs.js diff --git a/src/v3/handlers/shared/rewrite_refs.js b/src/v3/handlers/shared/rewrite_refs.js new file mode 100644 index 0000000..d62ab8c --- /dev/null +++ b/src/v3/handlers/shared/rewrite_refs.js @@ -0,0 +1,27 @@ +export default function rewrite_refs(node, parent, info) { + if (node.id.type === 'ObjectPattern') { + node.id.properties.forEach(prop => { + if (!info.refs.has(prop.key.name)) { + info.refs.set(prop.key.name, 'undefined'); + } + }); + + if (node.id.properties.every(node => node.shorthand)) { + if (parent.declarations.length !== 1) { + throw new Error(`TODO handle this.refs among other declarators`); + } + + let a = parent.start; + while (/\s/.test(info.source[a - 1])) a -= 1; + + info.code.remove(a, parent.end); + return; + } + } + + const { start, end } = node.init.callee.object; + info.code.overwrite(start, end, '__this'); + + info.uses_this = true; + info.uses_this_properties.add('refs'); +} \ No newline at end of file diff --git a/src/v3/handlers/shared/rewrite_this.js b/src/v3/handlers/shared/rewrite_this.js index 52074d4..6e57f0b 100644 --- a/src/v3/handlers/shared/rewrite_this.js +++ b/src/v3/handlers/shared/rewrite_this.js @@ -1,6 +1,7 @@ import { walk } from 'estree-walker'; import rewrite_set from './rewrite_set.js'; import rewrite_get from './rewrite_get.js'; +import rewrite_refs from './rewrite_refs.js'; // TODO rename this function, it does more than rewrite `this` export default function rewrite_this(node, info, is_event_handler, replacement = '__this') { @@ -22,7 +23,8 @@ export default function rewrite_this(node, info, is_event_handler, replacement = } if (is_this_property(child.init) && child.init.property.name === 'refs') { - throw new Error(`TODO handle 'const { ... } = this.refs'`); + rewrite_refs(child, parent, info); + return this.skip(); } } } diff --git a/src/v3/index.js b/src/v3/index.js index 92680a9..f776ac0 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -1,4 +1,4 @@ -import * as svelte from 'svelte'; +import * as svelte from 'svelte-2'; import MagicString from 'magic-string'; import { walk, childKeys } from 'estree-walker'; import handle_components from './handlers/components.js'; @@ -79,6 +79,7 @@ export function upgradeTemplate(source) { code, imported_functions: new Set(), props, + refs: new Map(), blocks: [], shared_blocks: [], imports: [], @@ -352,6 +353,11 @@ export function upgradeTemplate(source) { this_props.push(`get: () => ({ ${props.join(', ')} })`); } + if (info.uses_this_properties.has('refs')) { + const refs = Array.from(info.refs.keys()); + this_props.push(`refs: { ${refs.join(', ')} }`); + } + const rhs = this_props.length ? `{\n${indent}${indent}${this_props.join(`\n${indent}${indent}`)}\n${indent}}` : `{}`; From 4255ddf354a7ba522c7053a4099445eb7c7c206f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 29 Jan 2019 13:53:48 -0500 Subject: [PATCH 47/52] fix context=module --- src/v3/index.js | 3 +-- test/v3/samples/preload/output.html | 2 +- test/v3/samples/setup/output.html | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/v3/index.js b/src/v3/index.js index f776ac0..8c2e473 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -382,8 +382,7 @@ export function upgradeTemplate(source) { } if (info.shared_blocks.length > 0) { - // scope="shared" is subject to change - upgraded = `\n\n${upgraded}`; + upgraded = `\n\n${upgraded}`; } if (tag || namespace || immutable) { // TODO or bindings diff --git a/test/v3/samples/preload/output.html b/test/v3/samples/preload/output.html index 5c56d80..052e535 100644 --- a/test/v3/samples/preload/output.html +++ b/test/v3/samples/preload/output.html @@ -1,4 +1,4 @@ - \ No newline at end of file diff --git a/test/v3/samples/method-identifier/output.html b/test/v3/samples/method-identifier/output.html new file mode 100644 index 0000000..c7d4634 --- /dev/null +++ b/test/v3/samples/method-identifier/output.html @@ -0,0 +1,28 @@ + + + \ No newline at end of file From 28ab51c875a960556b90d97c46af350b0cd42088 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 29 Jan 2019 14:23:28 -0500 Subject: [PATCH 49/52] async methods --- src/v3/handlers/methods.js | 2 +- test/v3/samples/method-async/input.html | 19 +++++++++++++++++++ test/v3/samples/method-async/output.html | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/v3/samples/method-async/input.html create mode 100644 test/v3/samples/method-async/output.html diff --git a/src/v3/handlers/methods.js b/src/v3/handlers/methods.js index 2749717..5a54407 100644 --- a/src/v3/handlers/methods.js +++ b/src/v3/handlers/methods.js @@ -28,7 +28,7 @@ export default function handle_methods(node, info) { suggested = true; add_declaration(method.key, info); - blocks.push(`${suggestion}export ${node.async ? `async ` : ``}function ${method.key.name}${args} ${str}`); + blocks.push(`${suggestion}export ${method.value.async ? `async ` : ``}function ${method.key.name}${args} ${str}`); } else if (method.value.type === 'Identifier') { throw new Error(`TODO identifier methods`); } else { diff --git a/test/v3/samples/method-async/input.html b/test/v3/samples/method-async/input.html new file mode 100644 index 0000000..acf4dce --- /dev/null +++ b/test/v3/samples/method-async/input.html @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/test/v3/samples/method-async/output.html b/test/v3/samples/method-async/output.html new file mode 100644 index 0000000..7993cd1 --- /dev/null +++ b/test/v3/samples/method-async/output.html @@ -0,0 +1,19 @@ + + + \ No newline at end of file From d50479a591e36d4669b0edf5d73d385b26f2c23b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 29 Jan 2019 14:26:36 -0500 Subject: [PATCH 50/52] disregard shorthand --- src/v3/handlers/wrap_with_curlies.js | 4 ++++ test/v3/samples/binding-shorthand/input.html | 1 + test/v3/samples/binding-shorthand/output.html | 1 + 3 files changed, 6 insertions(+) create mode 100644 test/v3/samples/binding-shorthand/input.html create mode 100644 test/v3/samples/binding-shorthand/output.html diff --git a/src/v3/handlers/wrap_with_curlies.js b/src/v3/handlers/wrap_with_curlies.js index f85f323..5692f6a 100644 --- a/src/v3/handlers/wrap_with_curlies.js +++ b/src/v3/handlers/wrap_with_curlies.js @@ -1,4 +1,8 @@ export default function wrap_with_curlies(node, info) { + // disregard shorthand + const match = /^(\w+):(\w+)/.exec(info.code.original.slice(node.start, node.end)); + if (node.start + match[0].length === node.end) return; + const expression = node.expression || node.value; if (!expression) return; diff --git a/test/v3/samples/binding-shorthand/input.html b/test/v3/samples/binding-shorthand/input.html new file mode 100644 index 0000000..f24d608 --- /dev/null +++ b/test/v3/samples/binding-shorthand/input.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/v3/samples/binding-shorthand/output.html b/test/v3/samples/binding-shorthand/output.html new file mode 100644 index 0000000..f24d608 --- /dev/null +++ b/test/v3/samples/binding-shorthand/output.html @@ -0,0 +1 @@ + \ No newline at end of file From 412167480b7f0f95f899a57ad91e133b75ff6951 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 29 Jan 2019 15:14:44 -0500 Subject: [PATCH 51/52] use reactive declarations for computed props --- src/v3/handlers/computed.js | 68 +++++-------------- src/v3/index.js | 3 - src/v3/rewrite_computed.js | 27 -------- test/test.js | 5 +- test/v3/samples/computed-default/error.js | 2 +- .../computed-nested-destructuring/error.js | 11 +++ .../computed-nested-destructuring/output.html | 15 ---- .../computed-whole-state-rest/output.html | 11 ++- .../samples/computed-whole-state/output.html | 11 ++- test/v3/samples/computed/output.html | 21 +++--- 10 files changed, 53 insertions(+), 121 deletions(-) delete mode 100644 src/v3/rewrite_computed.js create mode 100644 test/v3/samples/computed-nested-destructuring/error.js delete mode 100644 test/v3/samples/computed-nested-destructuring/output.html diff --git a/src/v3/handlers/computed.js b/src/v3/handlers/computed.js index 41228db..02b47b0 100644 --- a/src/v3/handlers/computed.js +++ b/src/v3/handlers/computed.js @@ -6,7 +6,9 @@ export default function handle_computed(node, info) { const { props, code, blocks, indent } = info; node.properties.forEach(computed => { - let statements = []; + const { name } = computed.key; + + let chunks = []; const uses_whole_state = ( computed.value.params[0].type !== 'ObjectPattern' || @@ -14,48 +16,22 @@ export default function handle_computed(node, info) { ); if (uses_whole_state) { - statements = [ - `// [svelte-upgrade warning]\n${indent}${indent}// this function needs to be manually rewritten` - ]; + chunks.push( + `// [svelte-upgrade warning]\n${indent}// this function needs to be manually rewritten` + ); info.manual_edits_required = true; } else { - statements = computed.value.params[0].properties - .filter(param => param.value.type !== 'Identifier') - .map(param => { - if (param.value.type === 'AssignmentPattern') { - info.error(`svelte-upgrade cannot currently process default computed property arguments`, param.start); - } - - const { name } = param.key; - const lhs = code.slice(param.value.start, param.value.end); - const rhs = info.computed.has(name) ? `${name}()` : name; - return `const ${lhs} = ${rhs};` - }); - - // need to rewrite x => x() if x is computed - let { map, scope } = create_scopes(computed.value.body); - walk(computed.value.body, { - enter(node, parent) { - if (map.has(node)) { - scope = map.get(node); - } - - if (is_reference(node, parent)) { - if (info.computed.has(node.name)) { - code.appendLeft(node.end, '()'); - } - } - }, - - leave(node) { - if (map.has(node)) { - scope = scope.parent; - } + computed.value.params[0].properties.forEach(param => { + console.log(param); + if (param.type !== 'Property' || param.key !== param.value) { + info.error(`svelte-upgrade cannot currently process non-identifier computed property arguments`, param.start); } }); } + chunks.push(`export let ${computed.key.name};`); + const implicit_return = ( computed.value.type === 'ArrowFunctionExpression' && computed.value.body.type !== 'BlockStatement' @@ -63,24 +39,16 @@ export default function handle_computed(node, info) { if (implicit_return) { const expression = code.slice(computed.value.body.start, computed.value.body.end); - statements.push(`return ${expression};`); - - const body = statements.join(`\n${indent}${indent}`); - - blocks.push(`function ${computed.key.name}() {\n${indent}${indent}${body}\n${indent}}`); + chunks.push(`$: ${name} = ${expression};`); } else { - if (statements.length) { - const i = indent + indent + indent + indent; - - const declaration_block = statements.join(`\n${i}`); - code.appendLeft(computed.value.body.start + 1, `\n${i}${declaration_block}`); - } - const body = code.slice(computed.value.body.start, computed.value.body.end) .replace(info.indent_regex, '') - .replace(info.indent_regex, ''); + .replace(info.indent_regex, '') + .replace(`return`, `${name} =`); - blocks.push(`function ${computed.key.name}() ${body}`); + chunks.push(`$: ${body}`); } + + blocks.push(chunks.join(`\n${indent}`)); }); } \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 8c2e473..718d6da 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -14,7 +14,6 @@ import handle_setup from './handlers/setup.js'; import handle_store from './handlers/store.js'; import { find_declarations, get_code_frame } from './utils.js'; import { extract_names } from './scopes.js'; -import rewrite_computed from './rewrite_computed.js'; import handle_onstate_onupdate from './handlers/onstate_onupdate.js'; import add_declaration from './handlers/shared/add_declaration.js'; @@ -277,8 +276,6 @@ export function upgradeTemplate(source) { case 'MustacheTag': case 'RawMustacheTag': - // TODO also need to do this for expressions in blocks, attributes and directives - rewrite_computed(node, info, scope); break; case 'Ref': diff --git a/src/v3/rewrite_computed.js b/src/v3/rewrite_computed.js deleted file mode 100644 index 5cb0884..0000000 --- a/src/v3/rewrite_computed.js +++ /dev/null @@ -1,27 +0,0 @@ -import { walk } from 'estree-walker'; -import is_reference from 'is-reference'; -import { create_scopes } from './scopes.js'; - -export default function rewrite_computed(node, info, template_scope) { - let { map, scope } = create_scopes(node); - - walk(node, { - enter(node, parent) { - if (map.has(node)) { - scope = map.get(node); - } - - if (is_reference(node, parent)) { - if (!scope.has(node.name) && info.computed.has(node.name)) { - info.code.appendLeft(node.end, '()'); - } - } - }, - - leave(node) { - if (map.has(node)) { - scope = scope.parent; - } - } - }); -} \ No newline at end of file diff --git a/test/test.js b/test/test.js index 4d751a9..990bc11 100644 --- a/test/test.js +++ b/test/test.js @@ -41,7 +41,8 @@ function testVersion(v, upgrader) { expected_error.frame = expected_error.frame .replace('\n', '') - .replace(/^\t\t/gm, ''); + .replace(/^\t\t/gm, '') + .replace(/\s+$/gm, ''); if (err.code !== 'ENOENT') { t.equal(serialize_error(err), serialize_error(expected_error)); @@ -68,6 +69,6 @@ function serialize_error(err) { return JSON.stringify({ message: err.message, pos: err.pos, - frame: err.frame + frame: err.frame.replace(/\s+$/gm, '') }, null, ' '); } \ No newline at end of file diff --git a/test/v3/samples/computed-default/error.js b/test/v3/samples/computed-default/error.js index 24a2786..4f12305 100644 --- a/test/v3/samples/computed-default/error.js +++ b/test/v3/samples/computed-default/error.js @@ -1,5 +1,5 @@ module.exports = { - message: "svelte-upgrade cannot currently process default computed property arguments", + message: "svelte-upgrade cannot currently process non-identifier computed property arguments", pos: 72, frame: ` 4: export default { diff --git a/test/v3/samples/computed-nested-destructuring/error.js b/test/v3/samples/computed-nested-destructuring/error.js new file mode 100644 index 0000000..cb923ae --- /dev/null +++ b/test/v3/samples/computed-nested-destructuring/error.js @@ -0,0 +1,11 @@ +module.exports = { + message: "svelte-upgrade cannot currently process non-identifier computed property arguments", + pos: 132, + frame: ` + 8: }, + 9: + 10: len: ({ coords: { x, y } }) => { + ^ + 11: return Math.sqrt(x * x + y * y); + 12: }` +}; \ No newline at end of file diff --git a/test/v3/samples/computed-nested-destructuring/output.html b/test/v3/samples/computed-nested-destructuring/output.html deleted file mode 100644 index c081afe..0000000 --- a/test/v3/samples/computed-nested-destructuring/output.html +++ /dev/null @@ -1,15 +0,0 @@ - - -

len: {len()}

\ No newline at end of file diff --git a/test/v3/samples/computed-whole-state-rest/output.html b/test/v3/samples/computed-whole-state-rest/output.html index 34f0009..bda41d9 100644 --- a/test/v3/samples/computed-whole-state-rest/output.html +++ b/test/v3/samples/computed-whole-state-rest/output.html @@ -1,9 +1,8 @@ -
{JSON.stringify(props())}
\ No newline at end of file +
{JSON.stringify(props)}
\ No newline at end of file diff --git a/test/v3/samples/computed-whole-state/output.html b/test/v3/samples/computed-whole-state/output.html index 2c80bc6..adc4434 100644 --- a/test/v3/samples/computed-whole-state/output.html +++ b/test/v3/samples/computed-whole-state/output.html @@ -1,9 +1,8 @@ -

{b()}

\ No newline at end of file +

{b}

\ No newline at end of file diff --git a/test/v3/samples/computed/output.html b/test/v3/samples/computed/output.html index bd8ea21..ab160b6 100644 --- a/test/v3/samples/computed/output.html +++ b/test/v3/samples/computed/output.html @@ -2,19 +2,18 @@ export let a; export let b; - function c() { - return a + b; - } + export let c; + $: c = a + b; - function d() { - return c() * c(); - } + export let d; + $: d = c * c; - function e() { - return Math.pow(d(), d()); + export let e; + $: { + e = Math.pow(d, d); } -

{a} + {b} = {c()}

-

{c()} * {c()} = {d()}

-

{d()} ^ {d()} = {e()}

\ No newline at end of file +

{a} + {b} = {c}

+

{c} * {c} = {d}

+

{d} ^ {d} = {e}

\ No newline at end of file From fc4338bf3f057fa670ef4a1afb30a6bc59a1c974 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 29 Jan 2019 15:31:33 -0500 Subject: [PATCH 52/52] fix some stuff --- src/v3/handlers/computed.js | 1 - src/v3/handlers/wrap_with_curlies.js | 12 +++++++++--- src/v3/index.js | 5 ++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/v3/handlers/computed.js b/src/v3/handlers/computed.js index 02b47b0..6927de0 100644 --- a/src/v3/handlers/computed.js +++ b/src/v3/handlers/computed.js @@ -23,7 +23,6 @@ export default function handle_computed(node, info) { info.manual_edits_required = true; } else { computed.value.params[0].properties.forEach(param => { - console.log(param); if (param.type !== 'Property' || param.key !== param.value) { info.error(`svelte-upgrade cannot currently process non-identifier computed property arguments`, param.start); } diff --git a/src/v3/handlers/wrap_with_curlies.js b/src/v3/handlers/wrap_with_curlies.js index 5692f6a..346004c 100644 --- a/src/v3/handlers/wrap_with_curlies.js +++ b/src/v3/handlers/wrap_with_curlies.js @@ -1,7 +1,10 @@ export default function wrap_with_curlies(node, info) { + let { start, end } = node; + while (/\s/.test(info.source[end - 1])) end -= 1; + // disregard shorthand - const match = /^(\w+):(\w+)/.exec(info.code.original.slice(node.start, node.end)); - if (node.start + match[0].length === node.end) return; + const match = /^(\w+):(\w+)/.exec(info.code.original.slice(start, end)); + if (start + match[0].length === end) return; const expression = node.expression || node.value; @@ -9,6 +12,9 @@ export default function wrap_with_curlies(node, info) { const { code } = info; + end = expression.end; + while (/\s/.test(info.source[end - 1])) end -= 1; + code.appendLeft(expression.start, '{'); - code.prependRight(expression.end, '}'); + code.prependRight(end, '}'); } \ No newline at end of file diff --git a/src/v3/index.js b/src/v3/index.js index 718d6da..811112b 100644 --- a/src/v3/index.js +++ b/src/v3/index.js @@ -284,7 +284,10 @@ export function upgradeTemplate(source) { add_declaration(node, info); } - code.overwrite(node.start, node.end, `bind:this={${node.name}}`); + let { start, end } = node; + while (/\s/.test(source[end - 1])) end -= 1; + + code.overwrite(start, end, `bind:this={${node.name}}`); break; } },