diff --git a/.gitignore b/.gitignore
index 1135475..28936bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,6 @@
node_modules
*.d.ts
dist
-yarn-error.log
\ No newline at end of file
+yarn-error.log
+test/*/samples/_tmp
+_actual.html
\ No newline at end of file
diff --git a/package.json b/package.json
index c245d31..46ffa06 100644
--- a/package.json
+++ b/package.json
@@ -15,22 +15,26 @@
"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",
"dependencies": {
"estree-walker": "^0.5.2",
- "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",
- "svelte": "1",
- "turbocolor": "^2.6.1"
+ "svelte": "^2.15.2",
+ "svelte-1": "npm:svelte@1",
+ "svelte-2": "npm:svelte@2"
}
}
diff --git a/rollup.config.js b/rollup.config.js
index a00af87..7aae66c 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -5,10 +5,10 @@ export default {
input: ['src/index.js', 'src/cli.js'],
output: {
dir: 'dist',
- format: 'cjs'
+ format: 'cjs',
+ sourcemap: true
},
experimentalCodeSplitting: true,
- experimentalDynamicImport: true,
external: Object.keys(pkg.dependencies),
plugins: [
json()
diff --git a/src/cli.js b/src/cli.js
index be2fe7c..ffbab42 100644
--- a/src/cli.js
+++ b/src/cli.js
@@ -1,9 +1,8 @@
import fs from 'fs';
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);
@@ -19,72 +18,162 @@ 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];
+const valid_extensions = new Set([
+ '.html',
+ '.htmlx',
+ '.svelte'
+]);
+
+function get_tasks(items, in_dir, out_dir, arr = []) {
+ for (const item of items) {
+ const stats = fs.statSync(item);
+
+ if (stats.isDirectory()) {
+ get_tasks(
+ fs.readdirSync(item).map(file => path.resolve(item, file)),
+ path.join(in_dir, item),
+ path.join(out_dir, item),
+ arr
+ );
+ } else {
+ if (valid_extensions.has(path.extname(item))) {
+ arr.push({
+ input: item,
+ output: item.replace(in_dir, out_dir),
+ source: fs.readFileSync(item, 'utf-8')
+ });
}
+ }
+ }
- 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
- });
+ return arr;
+}
- if (response.value === false) {
- console.error(tc.cyan('Aborted'));
- return;
- }
+[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 function(_, opts) {
+ const tasks = get_tasks(
+ [_].concat(opts._).map(file => path.resolve(file)),
+ path.resolve('.'),
+ path.resolve(opts.output || '.')
+ );
+
+ if (opts.output && path.extname(opts.output)) {
+ // --output somefile.html
+ if (tasks.length > 1) {
+ console.error(c.bold.red(`--output must be a directory if more than one input is provided`));
+ } else {
+ // special case — file to file conversion
+ tasks[0].output = path.resolve(opts.output);
}
}
- input.forEach((src, i) => {
- const dest = output[i];
+ try {
+ const upgrade = v === 2
+ ? await import('./v2/index.js')
+ : await import('./v3/index.js');
+
+ if (!opts.force) {
+ const existing = tasks
+ .filter(task => fs.existsSync(task.output))
+ .map(task => path.relative(process.cwd(), task.output));
+
+ if (existing.length > 0) {
+ console.error(c.cyan(`This will overwrite the following files:`));
+ console.error(c.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(c.cyan('Aborted'));
+ return;
+ }
+ }
+ }
- try {
- const upgraded = upgrade.upgradeTemplate(fs.readFileSync(src, 'utf-8'));
+ 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);
+
+ 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, code);
+ } catch (error) {
+ if (error.name === 'UpgradeError') {
+ failed.push({ relative, error });
+ } else {
+ console.error(c.bold.red(`Error transforming ${relative}:`));
+ console.error(c.red(error.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));
+ let message = `Wrote ${count(tasks.length)}`;
- if (err.frame) {
- console.error(err.frame);
- }
+ if (unchanged_count > 0) {
+ message += `. ${count(unchanged_count)} required no changes`;
}
- });
- console.error(tc.cyan(`Wrote ${output.length} ${output.length === 1 ? 'file' : 'files'}`))
- } catch (err) {
- console.error(tc.red(err.message));
- }
- });
+ 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));
+ }
+ });
+});
+
+function count(num) {
+ return num === 1 ? `1 file` : `${num} files`;
+}
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(/`;
- }));
-
- 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(/`;
+ }));
+
+ 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/handlers/components.js b/src/v3/handlers/components.js
new file mode 100644
index 0000000..971bfff
--- /dev/null
+++ b/src/v3/handlers/components.js
@@ -0,0 +1,18 @@
+import alias_registration from './shared/alias_registration.js';
+
+export default function handle_components(node, info) {
+ const { blocks } = info;
+ const statements = [];
+
+ node.properties.forEach(component => {
+ if (component.value.type === 'Literal') {
+ statements.push(`import ${component.key.name} from '${component.value.value}';`);
+ } else {
+ alias_registration(component, info, statements, 'component');
+ }
+ });
+
+ if (statements.length > 0) {
+ blocks.push(statements.join(`\n${info.indent}`));
+ }
+}
\ No newline at end of file
diff --git a/src/v3/handlers/computed.js b/src/v3/handlers/computed.js
new file mode 100644
index 0000000..6927de0
--- /dev/null
+++ b/src/v3/handlers/computed.js
@@ -0,0 +1,53 @@
+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 { name } = computed.key;
+
+ let chunks = [];
+
+ const uses_whole_state = (
+ computed.value.params[0].type !== 'ObjectPattern' ||
+ computed.value.params[0].properties.some(x => x.type === 'RestElement')
+ );
+
+ if (uses_whole_state) {
+ chunks.push(
+ `// [svelte-upgrade warning]\n${indent}// this function needs to be manually rewritten`
+ );
+
+ info.manual_edits_required = true;
+ } else {
+ computed.value.params[0].properties.forEach(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'
+ );
+
+ if (implicit_return) {
+ const expression = code.slice(computed.value.body.start, computed.value.body.end);
+ chunks.push(`$: ${name} = ${expression};`);
+ } else {
+ const body = code.slice(computed.value.body.start, computed.value.body.end)
+ .replace(info.indent_regex, '')
+ .replace(info.indent_regex, '')
+ .replace(`return`, `${name} =`);
+
+ chunks.push(`$: ${body}`);
+ }
+
+ blocks.push(chunks.join(`\n${indent}`));
+ });
+}
\ 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..2c4193a
--- /dev/null
+++ b/src/v3/handlers/data.js
@@ -0,0 +1,61 @@
+import { walk } from 'estree-walker';
+
+export default function handle_data(node, 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);
+ }
+
+ let returned;
+
+ 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);
+ }
+
+ 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 => {
+ 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
new file mode 100644
index 0000000..5a54407
--- /dev/null
+++ b/src/v3/handlers/methods.js
@@ -0,0 +1,44 @@
+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, indent } = info;
+ const statements = [];
+
+ let suggested = false;
+
+ node.properties.forEach(method => {
+ if (method.value.type === 'FunctionExpression') {
+ const { params, body } = method.value;
+
+ rewrite_this(body, info);
+
+ const str = code.slice(body.start, body.end)
+ .replace(info.indent_regex, '')
+ .replace(info.indent_regex, '');
+
+ const args = params.length > 0
+ ? `(${code.slice(params[0].start, params[params.length - 1].end)})`
+ : '()';
+
+ const suggestion = suggested
+ ? ``
+ : `// [svelte-upgrade suggestion]\n${info.indent}// review these functions and remove unnecessary 'export' keywords\n${info.indent}`;
+
+ suggested = true;
+
+ add_declaration(method.key, info);
+ 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 {
+ error(`can only convert methods that are function expressions or references`, method);
+ }
+ });
+
+ if (statements.length > 0) {
+ blocks.push(`${statements.join(`\n${indent}`)}`);
+ }
+
+ info.manual_edits_suggested = true;
+}
\ No newline at end of file
diff --git a/src/v3/handlers/on_directive.js b/src/v3/handlers/on_directive.js
new file mode 100644
index 0000000..78c1200
--- /dev/null
+++ b/src/v3/handlers/on_directive.js
@@ -0,0 +1,56 @@
+import rewrite_this from './shared/rewrite_this.js';
+import { walk } from 'estree-walker';
+
+const voidElementNames = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/;
+
+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 (callee.name === 'fire') {
+ info.uses_dispatch = true;
+ code.overwrite(callee.start, callee.end, 'dispatch');
+ }
+
+ 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);
+
+ const this_replacement = voidElementNames.test(parent.name)
+ ? `event.target`
+ : `event.currentTarget`;
+
+ rewrite_this(node.expression, info, true, this_replacement);
+
+ code.prependRight(start, uses_event ? `event => ` : `() => `);
+ }
+
+ let a = start;
+ while (code.original[a - 1] !== '=') a -= 1;
+ const has_quote = a !== start;
+
+ const needs_quote = !has_quote && (
+ /\s/.test(code.slice(start, end)) ||
+ args.length > 0
+ );
+
+ code.appendLeft(start, needs_quote ? '"{' : '{');
+ code.prependRight(end, needs_quote ? '}"' : '}');
+}
+
+function find_event(expression) {
+ let found = false;
+
+ walk(expression, {
+ enter(node) {
+ if (node.type === 'Identifier' && node.name === 'event') {
+ found = true;
+ }
+ }
+ });
+
+ return found;
+}
\ No newline at end of file
diff --git a/src/v3/handlers/oncreate_ondestroy.js b/src/v3/handlers/oncreate_ondestroy.js
new file mode 100644
index 0000000..2b2b924
--- /dev/null
+++ b/src/v3/handlers/oncreate_ondestroy.js
@@ -0,0 +1,19 @@
+import rewrite_this from './shared/rewrite_this.js';
+
+export default function handle_oncreate_ondestroy(node, info, name) {
+ const { code, blocks, imported_functions, indent_regex } = info;
+
+ imported_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(`${name}(${node.async ? `async ` : ``}() => ${body});`);
+ }
+
+ else {
+ 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
new file mode 100644
index 0000000..c87f92b
--- /dev/null
+++ b/src/v3/handlers/onstate_onupdate.js
@@ -0,0 +1,23 @@
+import rewrite_this from './shared/rewrite_this.js';
+
+export default function handle_onstate_onupdate(node, info, name) {
+ const { code, blocks, imported_functions, indent, indent_regex } = info;
+
+ imported_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}// beforeUpdate and afterUpdate handlers behave\n${indent}// differently to their v2 counterparts\n${indent}${name}(${node.async ? `async ` : ``}() => ${body});`);
+ }
+
+ else {
+ const body = code.slice(node.start, node.end).replace(indent_regex, '');
+ blocks.push(`${name}(${body});`);
+ }
+
+ info.manual_edits_required = true;
+}
\ No newline at end of file
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/handlers/setup.js b/src/v3/handlers/setup.js
new file mode 100644
index 0000000..15719c2
--- /dev/null
+++ b/src/v3/handlers/setup.js
@@ -0,0 +1,10 @@
+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/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
new file mode 100644
index 0000000..6e050bc
--- /dev/null
+++ b/src/v3/handlers/shared/alias_registration.js
@@ -0,0 +1,12 @@
+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;
+ }
+
+ add_declaration(node.key, info);
+
+ 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/handlers/shared/handle_registrants.js b/src/v3/handlers/shared/handle_registrants.js
new file mode 100644
index 0000000..e89e018
--- /dev/null
+++ b/src/v3/handlers/shared/handle_registrants.js
@@ -0,0 +1,41 @@
+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, indent } = info;
+ const statements = [];
+
+ registrants.forEach(registrant => {
+ const { key, value } = registrant;
+
+ if (value.type === 'FunctionExpression') {
+ const { params, body } = value;
+
+ rewrite_this(body, info);
+
+ const str = code.slice(body.start, body.end)
+ .replace(info.indent_regex, '')
+ .replace(info.indent_regex, '');
+
+ const args = params.length > 0
+ ? `(${code.slice(params[0].start, params[params.length - 1].end)})`
+ : '()';
+
+ add_declaration(key, info);
+ blocks.push(`function ${key.name}${args} ${str}`);
+ } else if (value.type === 'Identifier') {
+ alias_registration(registrant, info, statements, type);
+ } else {
+ const str = code.slice(value.start, value.end)
+ .replace(info.indent_regex, '')
+ .replace(info.indent_regex, '');
+
+ blocks.push(`const ${key.name} = ${str};`);
+ }
+ });
+
+ if (statements.length > 0) {
+ blocks.push(`${statements.join(`\n${indent}`)}`);
+ }
+}
\ No newline at end of file
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_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_set.js b/src/v3/handlers/shared/rewrite_set.js
new file mode 100644
index 0000000..57db05b
--- /dev/null
+++ b/src/v3/handlers/shared/rewrite_set.js
@@ -0,0 +1,30 @@
+export default function rewrite_set(node, info) {
+ if (node.arguments.length !== 1) {
+ info.error(`expected a single argument`, node.start);
+ }
+
+ if (node.arguments[0].type !== 'ObjectExpression') {
+ info.error(`expected an object literal`, node.arguments[0].start);
+ }
+
+ const { properties } = node.arguments[0];
+
+ const assignments = properties
+ .map(prop => {
+ // special case — `x: x + 1`
+ const is_increment = (
+ prop.value.type === 'BinaryExpression' &&
+ prop.value.right.value === 1 &&
+ /[-+]/.test(prop.value.operator)
+ );
+
+ if (is_increment) {
+ return `${prop.key.name} ${prop.value.operator}= 1`;
+ }
+
+ return `${prop.key.name} = ${info.code.slice(prop.value.start, prop.value.end)}`;
+ })
+ .join(', ');
+
+ info.code.overwrite(node.start, node.end, assignments);
+}
\ No newline at end of file
diff --git a/src/v3/handlers/shared/rewrite_this.js b/src/v3/handlers/shared/rewrite_this.js
new file mode 100644
index 0000000..6e57f0b
--- /dev/null
+++ b/src/v3/handlers/shared/rewrite_this.js
@@ -0,0 +1,97 @@
+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') {
+ const { code, methods } = info;
+
+ walk(node, {
+ enter(child, parent) {
+ if (/^Function/.test(child.type)) {
+ this.skip();
+ }
+
+ if (child.type === 'VariableDeclarator') {
+ 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') {
+ rewrite_refs(child, parent, info);
+ return this.skip();
+ }
+ }
+ }
+
+ else if (child.type === 'CallExpression') {
+ if (is_method(child.callee, 'set', is_event_handler)) {
+ rewrite_set(child, info);
+ return this.skip();
+ }
+
+ // TODO optimise get
+ }
+
+ else if (is_this_property(child)) {
+ if (!child.property.computed) {
+ if (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`);
+ return this.skip();
+
+ default:
+ code.overwrite(child.object.start, child.object.end, replacement);
+ }
+
+ info.uses_this = true;
+ info.uses_this_properties.add(child.property.name);
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+ }
+ });
+}
+
+function is_this_property(node) {
+ return (
+ node.type === 'MemberExpression' &&
+ node.object.type === 'ThisExpression' &&
+ !node.property.computed
+ );
+}
+
+function is_method(callee, name, is_event_handler) {
+ if (is_event_handler) {
+ return callee.type === 'Identifier' && callee.name === name;
+ }
+
+ return (
+ is_this_property(callee) &&
+ callee.property.name === name &&
+ !callee.property.computed
+ );
+}
\ 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/handlers/wrap_with_curlies.js b/src/v3/handlers/wrap_with_curlies.js
new file mode 100644
index 0000000..346004c
--- /dev/null
+++ b/src/v3/handlers/wrap_with_curlies.js
@@ -0,0 +1,20 @@
+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(start, end));
+ if (start + match[0].length === end) return;
+
+ const expression = node.expression || node.value;
+
+ if (!expression) return;
+
+ const { code } = info;
+
+ end = expression.end;
+ while (/\s/.test(info.source[end - 1])) end -= 1;
+
+ code.appendLeft(expression.start, '{');
+ code.prependRight(end, '}');
+}
\ No newline at end of file
diff --git a/src/v3/index.js b/src/v3/index.js
new file mode 100644
index 0000000..811112b
--- /dev/null
+++ b/src/v3/index.js
@@ -0,0 +1,404 @@
+import * as svelte from 'svelte-2';
+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_ondestroy from './handlers/oncreate_ondestroy.js';
+import handle_on_directive from './handlers/on_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';
+import handle_store from './handlers/store.js';
+import { find_declarations, get_code_frame } from './utils.js';
+import { extract_names } from './scopes.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
+// the wrong idea about the shape of each/if blocks
+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',
+]);
+
+class UpgradeError extends Error {}
+
+export function upgradeTemplate(source) {
+ const code = new MagicString(source);
+ const result = svelte.compile(source, {
+ generate: false
+ });
+
+ const indent = code.getIndentString();
+
+ let tag;
+ let namespace;
+ let immutable;
+ let script_sections = [];
+
+ const props = new Map();
+ result.stats.props.forEach(prop => {
+ if (!global_whitelist.has(prop) && prop[0] !== '$') {
+ props.set(prop, 'undefined');
+ }
+ });
+
+ const info = {
+ source,
+ code,
+ imported_functions: new Set(),
+ props,
+ refs: new Map(),
+ blocks: [],
+ shared_blocks: [],
+ imports: [],
+ methods: new Set(),
+ computed: new Set(),
+ helpers: new Set(),
+ declarations: new Set(),
+ indent,
+ indent_regex: new RegExp(`^${indent}`, 'gm'),
+ uses_this: false,
+ uses_dispatch: false,
+ uses_this_properties: new Set(),
+
+ manual_edits_required: false,
+ manual_edits_suggested: false,
+
+ error(message, pos) {
+ const e = new UpgradeError(message);
+ e.name = 'UpgradeError';
+ e.pos = pos;
+ e.frame = get_code_frame(source, pos);
+
+ throw e;
+ }
+ };
+
+ const body = result.ast.js && result.ast.js.content.body;
+ const default_export = body && body.find(node => node.type === 'ExportDefaultDeclaration');
+
+ if (body) find_declarations(body, info.declarations);
+
+ if (default_export) {
+ // 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);
+ });
+ }
+
+ if (prop.key.name === 'computed') {
+ 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);
+ });
+ }
+ });
+
+ 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, 'beforeUpdate');
+ break;
+
+ case 'onupdate':
+ handle_onstate_onupdate(prop.value, info, 'afterUpdate');
+ 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}`};`);
+ }
+
+ 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();
+ const scopes = [scope];
+
+ const refs = new Set();
+
+ 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;
+
+ case 'Action':
+ case 'Binding':
+ case 'Class':
+ case 'Transition':
+ wrap_with_curlies(node, info, parent);
+ break;
+
+ case 'MustacheTag':
+ case 'RawMustacheTag':
+ break;
+
+ case 'Ref':
+ if (!refs.has(node.name)) {
+ refs.add(node.name);
+ add_declaration(node, info);
+ }
+
+ let { start, end } = node;
+ while (/\s/.test(source[end - 1])) end -= 1;
+
+ code.overwrite(start, end, `bind:this={${node.name}}`);
+ break;
+ }
+ },
+
+ leave(node) {
+ if (node.type === 'EachBlock' || node.type === 'ThenBlock' || node.type === 'CatchBlock') {
+ scopes.pop();
+ scope = scopes[scopes.length - 1];
+ }
+ }
+ });
+
+ 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 (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];
+ 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(', ')} })`);
+ }
+
+ 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}}`
+ : `{}`;
+
+ 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) {
+ upgraded = `\n\n${upgraded}`;
+ }
+
+ if (info.shared_blocks.length > 0) {
+ upgraded = `\n\n${upgraded}`;
+ }
+
+ 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}`;
+ }
+
+ const eof_newline = /(\r?\n)?$/.exec(source)[1] || '';
+
+ return {
+ code: upgraded.trim() + eof_newline,
+ manual_edits_required: info.manual_edits_required,
+ manual_edits_suggested: info.manual_edits_suggested
+ };
+}
\ No newline at end of file
diff --git a/src/v3/scopes.js b/src/v3/scopes.js
new file mode 100644
index 0000000..ab2a918
--- /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))
+ );
+ }
+}
+
+export 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/src/v3/utils.js b/src/v3/utils.js
new file mode 100644
index 0000000..8e4b7f8
--- /dev/null
+++ b/src/v3/utils.js
@@ -0,0 +1,91 @@
+import { walk } from 'estree-walker';
+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') {
+ node.specifiers.forEach(specifier => {
+ declarations.add(specifier.local.name);
+ });
+ }
+
+ else if (node.type === 'ClassDeclaration') {
+ declarations.add(node.id.name);
+ }
+
+ else if (node.type === 'FunctionDeclaration') {
+ declarations.add(node.id.name);
+ this.skip();
+ }
+
+ else if (node.type === 'VariableDeclaration') {
+ 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;
+ }
+ }
+ });
+
+ return declarations;
+}
+
+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;
+
+ 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}`;
+ }
+
+ 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 70e6c15..990bc11 100644
--- a/test/test.js
+++ b/test/test.js
@@ -1,16 +1,74 @@
import * as fs from 'fs';
+import * as path from 'path';
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;
+const args = process.argv.slice(2);
- 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');
+const versions = new Set(args.filter(x => /^v\d$/.test(x)));
+if (versions.size === 0) {
+ versions.add('v2');
+ versions.add('v3');
+}
- const actual = upgradeTemplate(source);
+const tests = new Set(args.filter(x => !/^v\d$/.test(x)));
- t.equal(actual, expected);
+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_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 source = fs.readFileSync(source_file, 'utf-8');
+
+ let actual;
+ let expected;
+
+ try {
+ 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)) {
+ const expected_error = require(path.resolve(error_file));
+
+ expected_error.frame = expected_error.frame
+ .replace('\n', '')
+ .replace(/^\t\t/gm, '')
+ .replace(/\s+$/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);
+ });
});
-});
\ No newline at end of file
+}
+
+if (versions.has('v2')) testVersion(2, v2);
+if (versions.has('v3')) testVersion(3, v3);
+
+function serialize_error(err) {
+ return JSON.stringify({
+ message: err.message,
+ pos: err.pos,
+ frame: err.frame.replace(/\s+$/gm, '')
+ }, null, ' ');
+}
\ 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/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/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
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/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/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
diff --git a/test/v3/samples/computed-default/error.js b/test/v3/samples/computed-default/error.js
new file mode 100644
index 0000000..4f12305
--- /dev/null
+++ b/test/v3/samples/computed-default/error.js
@@ -0,0 +1,11 @@
+module.exports = {
+ message: "svelte-upgrade cannot currently process non-identifier 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
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/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-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..bda41d9
--- /dev/null
+++ b/test/v3/samples/computed-whole-state-rest/output.html
@@ -0,0 +1,8 @@
+
+
+
{JSON.stringify(props)}
\ No newline at end of file
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..adc4434
--- /dev/null
+++ b/test/v3/samples/computed-whole-state/output.html
@@ -0,0 +1,8 @@
+
+
+
{b}
\ 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..ab160b6
--- /dev/null
+++ b/test/v3/samples/computed/output.html
@@ -0,0 +1,19 @@
+
+
+
{a} + {b} = {c}
+
{c} * {c} = {d}
+
{d} ^ {d} = {e}
\ 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-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/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/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
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
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/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/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
diff --git a/test/v3/samples/event-handler-set/input.html b/test/v3/samples/event-handler-set/input.html
new file mode 100644
index 0000000..086a07c
--- /dev/null
+++ b/test/v3/samples/event-handler-set/input.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/test/v3/samples/event-handler-set/output.html b/test/v3/samples/event-handler-set/output.html
new file mode 100644
index 0000000..f6bdd16
--- /dev/null
+++ b/test/v3/samples/event-handler-set/output.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
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/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/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
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/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..c3e1639
--- /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..32ce194
--- /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
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
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..0c22bd6
--- /dev/null
+++ b/test/v3/samples/helper-non-function/output.html
@@ -0,0 +1,11 @@
+
+
+{Math.min(x, 5)}
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
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
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
diff --git a/test/v3/samples/method-identifier/input.html b/test/v3/samples/method-identifier/input.html
new file mode 100644
index 0000000..c537f96
--- /dev/null
+++ b/test/v3/samples/method-identifier/input.html
@@ -0,0 +1,22 @@
+
+
+
\ 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
diff --git a/test/v3/samples/method/input.html b/test/v3/samples/method/input.html
new file mode 100644
index 0000000..30d5de2
--- /dev/null
+++ b/test/v3/samples/method/input.html
@@ -0,0 +1,19 @@
+
+
+
\ 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..5a2530c
--- /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/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/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..f75c6cc
--- /dev/null
+++ b/test/v3/samples/oncreate-arrow/output.html
@@ -0,0 +1,7 @@
+
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..3b52e0a
--- /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/input.html b/test/v3/samples/oncreate/input.html
new file mode 100644
index 0000000..d99e2dd
--- /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..4a5a69e
--- /dev/null
+++ b/test/v3/samples/oncreate/output.html
@@ -0,0 +1,13 @@
+
+
+
component with oncreate
\ No newline at end of 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..3b62111
--- /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..9267ac7
--- /dev/null
+++ b/test/v3/samples/onupdate/output.html
@@ -0,0 +1,14 @@
+
+
+
component with onupdate
\ No newline at end of file
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..052e535
--- /dev/null
+++ b/test/v3/samples/preload/output.html
@@ -0,0 +1,12 @@
+
+
+
+
+
Hello {user.name}
\ No newline at end of file
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!
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..fa854dc
--- /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..fa854dc
--- /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..b997d2e
--- /dev/null
+++ b/test/v3/samples/ref/output.html
@@ -0,0 +1 @@
+
\ No newline at end of file
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/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..04faa5b
--- /dev/null
+++ b/test/v3/samples/setup/output.html
@@ -0,0 +1,14 @@
+
+
+
text
\ No newline at end of file
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 @@
+
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 @@
+
+
+