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(//gm, m => { - let spaces = ''; - let i = m.length - 15; - while (i-- > 0) spaces += ' '; - - return ``; - })); - - function trimStart(node) { - let c = node.start; - - code.remove(c, c + 1); - - c = node.expression.end; - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - function trimEnd(node) { - let c = node.end; - - code.remove(c - 1, c); - - while (source[c - 1] !== '{') c -= 1; - code.remove(c - 1, c); - } - - function trim(node) { - trimStart(node); - trimEnd(node); - } - - const properties = {}; - const methods = {}; - - if (ast.js) { - const defaultExport = ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); - if (defaultExport) { - defaultExport.declaration.properties.forEach(prop => { - properties[prop.key.name] = prop.value; - }); - - if (properties.computed) { - properties.computed.properties.forEach(prop => { - const { params } = prop.value; - - if (prop.value.type === 'FunctionExpression') { - let a = prop.value.start; - if (!prop.method) a += 8; - while (source[a] !== '(') a += 1; - - let b = params[0].start; - code.overwrite(a, b, '({ '); - - a = b = params[params.length - 1].end; - while (source[b] !== ')') b += 1; - code.overwrite(a, b + 1, ' })'); - } else if (prop.value.type === 'ArrowFunctionExpression') { - let a = prop.value.start; - let b = params[0].start; - - if (a !== b) code.remove(a, b); - code.prependRight(b, '({ '); - - a = b = params[params.length - 1].end; - while (source[b] !== '=') b += 1; - - if (a !== b) code.remove(a, b); - code.appendLeft(a, ' }) '); - } - }); - } - - if (properties.methods) { - properties.methods.properties.forEach(prop => { - methods[prop.key.name] = prop.value; - }); - } - } - } - - walk(ast.html, { - enter(node) { - let a = node.start; - let b = node.end; - - switch (node.type) { - case 'MustacheTag': - trimStart(node); - break; - - case 'RawMustacheTag': - code.overwrite(a + 1, node.expression.start, '@html ').remove(b - 2, b); - break; - - case 'AwaitBlock': - trim(node); - - if (node.pending.start !== null) { - let c = node.then.start; - code.overwrite(c + 1, c + 2, ':'); - - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - if (node.catch.start !== null) { - let c = node.catch.start; - code.overwrite(c + 1, c + 2, ':'); - - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - break; - - case 'IfBlock': - case 'EachBlock': - if (!node.skip) trim(node); - - if (node.else) { - let c = node.children[node.children.length - 1].end; - while (source[c] !== '{') c += 1; - code.overwrite(c + 1, c + 2, ':'); - - if (hasElseIf(source, node)) { - c = node.else.children[0].expression.end; - node.else.children[0].skip = true; - } - - while (source[c] !== '}') c += 1; - code.remove(c, c + 1); - } - - if (node.key) { - let a = node.expression.end; - while (source[a] !== '@') a += 1; - code.overwrite(a, a + 1, `(${node.context}.`); - - while (!/\w/.test(source[a])) a += 1; - while (/\w/.test(source[a])) a += 1; - code.appendLeft(a, ')'); - } - - break; - - case 'Element': - case 'Window': - case 'Head': - if (node.name === 'slot' && /{{\s*yield\s*}}/.test(source.slice(a, b))) { - code.overwrite(a, b, ''); - } - - else if (node.name[0] === ':') { - const name = `svelte:${node.name[1].toLowerCase()}`; - code.overwrite(a + 1, a + 3, name); - - while (source[b - 1] !== '<') b -= 1; - if (source[b] === '/') { - code.overwrite(b + 1, b + 3, name); - } - } - - if (node.name === ':Component') { - a = node.expression.start; - while (source[a - 1] !== '{') a -= 1; - - b = node.expression.end; - while (source[b] !== '}') b += 1; - - const shouldQuote = /\s/.test(source.slice(a, b)); - if (shouldQuote) code.prependRight(a - 1, '"').appendLeft(b + 1, '"'); - code.prependRight(a - 1, 'this=') - } - - break; - - case 'Text': - let c = -1; - while ((c = node.data.indexOf('{', c + 1)) !== -1) { - code.overwrite(a + c, a + c + 1, '{'); - } - break; - - case 'Attribute': - if (source[a] === ':') { - code.overwrite(a, a + 1, '{').appendLeft(b, '}'); - } - - break; - - case 'Spread': - code.remove(a, a + 1).remove(b - 1, b); - break; - - case 'EventHandler': - if (node.expression) { - const { name, parts } = flattenReference(node.expression.callee); - if (name === 'store') { - if (`$${parts[1].name}` in methods) { - console.error(`Not overwriting store method — $${parts[1].name} already exists on component`); - } else { - code.overwrite(node.expression.start, parts[1].start, '$'); - } - } - } - - break; - } - }, - - leave(node) { - - } - }); - - - - return code.toString(); -} \ No newline at end of file +export { upgradeTemplate as v2 } from './v2/index.js'; +export { upgradeTemplate as v3 } from './v3/index.js'; \ No newline at end of file diff --git a/src/v2/index.js b/src/v2/index.js new file mode 100644 index 0000000..95c1c7f --- /dev/null +++ b/src/v2/index.js @@ -0,0 +1,267 @@ +import * as svelte from 'svelte-1'; +import MagicString from 'magic-string'; +import { walk, childKeys } from 'estree-walker'; + +// We need to tell estree-walker that it should always +// look for an `else` block, otherwise it might get +// the wrong idea about the shape of each/if blocks +childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; +childKeys.Attribute = ['value']; + +function hasElseIf(source, node) { + const last = node.children[node.children.length - 1]; + + let c = last.end; + while (source[c] !== '{') c += 1; + while (source[c] === '{') c += 1; + while (/\s/.test(source[c])) c += 1; + + return source.slice(c, c + 6) === 'elseif'; +} + +function flattenReference(node) { + const parts = []; + const propEnd = node.end; + + while (node.type === 'MemberExpression') { + if (node.computed) return null; + parts.unshift(node.property); + + node = node.object; + } + + parts.unshift(node); + + const propStart = node.end; + const name = node.type === 'Identifier' + ? node.name + : node.type === 'ThisExpression' ? 'this' : null; + + if (!name) return null; + + return { name, parts }; +} + + +export function upgradeTemplate(source) { + const code = new MagicString(source); + const ast = svelte.parse(source.replace(//gm, m => { + let spaces = ''; + let i = m.length - 15; + while (i-- > 0) spaces += ' '; + + return ``; + })); + + function trimStart(node) { + let c = node.start; + + code.remove(c, c + 1); + + c = node.expression.end; + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + function trimEnd(node) { + let c = node.end; + + code.remove(c - 1, c); + + while (source[c - 1] !== '{') c -= 1; + code.remove(c - 1, c); + } + + function trim(node) { + trimStart(node); + trimEnd(node); + } + + const properties = {}; + const methods = {}; + + if (ast.js) { + const defaultExport = ast.js.content.body.find(node => node.type === 'ExportDefaultDeclaration'); + if (defaultExport) { + defaultExport.declaration.properties.forEach(prop => { + properties[prop.key.name] = prop.value; + }); + + if (properties.computed) { + properties.computed.properties.forEach(prop => { + const { params } = prop.value; + + if (prop.value.type === 'FunctionExpression') { + let a = prop.value.start; + if (!prop.method) a += 8; + while (source[a] !== '(') a += 1; + + let b = params[0].start; + code.overwrite(a, b, '({ '); + + a = b = params[params.length - 1].end; + while (source[b] !== ')') b += 1; + code.overwrite(a, b + 1, ' })'); + } else if (prop.value.type === 'ArrowFunctionExpression') { + let a = prop.value.start; + let b = params[0].start; + + if (a !== b) code.remove(a, b); + code.prependRight(b, '({ '); + + a = b = params[params.length - 1].end; + while (source[b] !== '=') b += 1; + + if (a !== b) code.remove(a, b); + code.appendLeft(a, ' }) '); + } + }); + } + + if (properties.methods) { + properties.methods.properties.forEach(prop => { + methods[prop.key.name] = prop.value; + }); + } + } + } + + walk(ast.html, { + enter(node) { + let a = node.start; + let b = node.end; + + switch (node.type) { + case 'MustacheTag': + trimStart(node); + break; + + case 'RawMustacheTag': + code.overwrite(a + 1, node.expression.start, '@html ').remove(b - 2, b); + break; + + case 'AwaitBlock': + trim(node); + + if (node.pending.start !== null) { + let c = node.then.start; + code.overwrite(c + 1, c + 2, ':'); + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + if (node.catch.start !== null) { + let c = node.catch.start; + code.overwrite(c + 1, c + 2, ':'); + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + break; + + case 'IfBlock': + case 'EachBlock': + if (!node.skip) trim(node); + + if (node.else) { + let c = node.children[node.children.length - 1].end; + while (source[c] !== '{') c += 1; + code.overwrite(c + 1, c + 2, ':'); + + if (hasElseIf(source, node)) { + c = node.else.children[0].expression.end; + node.else.children[0].skip = true; + } + + while (source[c] !== '}') c += 1; + code.remove(c, c + 1); + } + + if (node.key) { + let a = node.expression.end; + while (source[a] !== '@') a += 1; + code.overwrite(a, a + 1, `(${node.context}.`); + + while (!/\w/.test(source[a])) a += 1; + while (/\w/.test(source[a])) a += 1; + code.appendLeft(a, ')'); + } + + break; + + case 'Element': + case 'Window': + case 'Head': + if (node.name === 'slot' && /{{\s*yield\s*}}/.test(source.slice(a, b))) { + code.overwrite(a, b, ''); + } + + else if (node.name[0] === ':') { + const name = `svelte:${node.name[1].toLowerCase()}`; + code.overwrite(a + 1, a + 3, name); + + while (source[b - 1] !== '<') b -= 1; + if (source[b] === '/') { + code.overwrite(b + 1, b + 3, name); + } + } + + if (node.name === ':Component') { + a = node.expression.start; + while (source[a - 1] !== '{') a -= 1; + + b = node.expression.end; + while (source[b] !== '}') b += 1; + + const shouldQuote = /\s/.test(source.slice(a, b)); + if (shouldQuote) code.prependRight(a - 1, '"').appendLeft(b + 1, '"'); + code.prependRight(a - 1, 'this=') + } + + break; + + case 'Text': + let c = -1; + while ((c = node.data.indexOf('{', c + 1)) !== -1) { + code.overwrite(a + c, a + c + 1, '{'); + } + break; + + case 'Attribute': + if (source[a] === ':') { + code.overwrite(a, a + 1, '{').appendLeft(b, '}'); + } + + break; + + case 'Spread': + code.remove(a, a + 1).remove(b - 1, b); + break; + + case 'EventHandler': + if (node.expression) { + const { name, parts } = flattenReference(node.expression.callee); + if (name === 'store') { + if (`$${parts[1].name}` in methods) { + console.error(`Not overwriting store method — $${parts[1].name} already exists on component`); + } else { + code.overwrite(node.expression.start, parts[1].start, '$'); + } + } + } + + break; + } + }, + + leave(node) { + + } + }); + + + + return code.toString(); +} \ No newline at end of file diff --git a/src/v3/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 @@ +

Hello {$name}!

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

Hello {$name}!

diff --git a/test/v3/samples/tag/input.html b/test/v3/samples/tag/input.html new file mode 100644 index 0000000..60b7ae5 --- /dev/null +++ b/test/v3/samples/tag/input.html @@ -0,0 +1,7 @@ +

I am a custom element

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

I am a custom element

\ No newline at end of file diff --git a/test/v3/samples/transition/input.html b/test/v3/samples/transition/input.html new file mode 100644 index 0000000..a181643 --- /dev/null +++ b/test/v3/samples/transition/input.html @@ -0,0 +1,9 @@ +
+ + \ No newline at end of file diff --git a/test/v3/samples/transition/output.html b/test/v3/samples/transition/output.html new file mode 100644 index 0000000..d4bd7bb --- /dev/null +++ b/test/v3/samples/transition/output.html @@ -0,0 +1,5 @@ + + +
\ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f4ca0cc..0a132c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,15 +2,20 @@ # yarn lockfile v1 +"@types/estree@0.0.38": + version "0.0.38" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2" + integrity sha512-F/v7t1LwS4vnXuPooJQGBRKRGIoxWUTmA4VHfqjOccFsNDThD5bfUNpITive6s352O7o384wcpEaDV8rHCehDA== + "@types/estree@0.0.39": version "0.0.39" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== "@types/node@*": - version "10.12.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.2.tgz#d77f9faa027cadad9c912cd47f4f8b07b0fb0864" - integrity sha512-53ElVDSnZeFUUFIYzI8WLQ25IhWzb6vbddNp8UHlXQyU0ET2RhV5zg0NfubzU7iNMh5bBXb0htCzfvrSVNgzaQ== + version "10.12.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.10.tgz#4fa76e6598b7de3f0cb6ec3abacc4f59e5b3a2ce" + integrity sha512-8xZEYckCbUVgK8Eg7lf5Iy4COKJ5uXlnIOnePN0WUwSQggy9tolM+tDJf7wMOnT/JT/W9xDYIaYggt3mRV2O5w== arr-diff@^2.0.0: version "2.0.0" @@ -29,19 +34,6 @@ array-unique@^0.2.1: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -51,10 +43,10 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== esm@^3.0.84: version "3.0.84" @@ -93,13 +85,13 @@ filename-regex@^2.0.0: integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - integrity sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM= + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" @@ -115,11 +107,6 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - glob-base@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -135,31 +122,6 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -201,12 +163,10 @@ is-number@^2.1.0: dependencies: kind-of "^3.0.2" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== is-posix-bracket@^0.1.0: version "0.1.1" @@ -218,6 +178,13 @@ is-primitive@^2.0.0: resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= +is-reference@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.0.tgz#50e6ef3f64c361e2c53c0416cdc9420037f2685b" + integrity sha512-h37O/IX4efe56o9k41II1ECMqKwtqHa7/12dLDEzJIFux2x15an4WCDb0/eKdmUgRpLJ3bR0DrzDc7vOrVgRDw== + dependencies: + "@types/estree" "0.0.38" + isarray@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -237,18 +204,26 @@ kind-of@^3.0.2: dependencies: is-buffer "^1.1.5" -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" +kind-of@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== -kleur@^2.0.1: +kleur@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +kleur@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.0.tgz#ae185c2e0641ffb2f8e3d182df13239df9d8d3b2" + integrity sha512-acHc4xKlqTNuAGmbvtd3KuNi1bnlHsdPg6Os1P5s/Ii/6g8MY3caVPDp4md04/twbh4wwPvdpnol1bc9zFsI3w== + +locate-character@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/locate-character/-/locate-character-2.0.5.tgz#f2d2614d49820ecb3c92d80d193b8db755f74c0f" + integrity sha512-n2GmejDXtOPBAZdIiEFy5dJ5N38xBCXLNOtw2WpB9kGh6pnrEuKlwYI+Tkpofc4wDtVXHtoAOJaMRlYG/oYaxg== + magic-string@^0.25.1: version "0.25.1" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.1.tgz#b1c248b399cd7485da0fe7385c2fc7011843266e" @@ -256,6 +231,11 @@ magic-string@^0.25.1: dependencies: sourcemap-codec "^1.4.1" +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= + micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" @@ -275,17 +255,10 @@ micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - mri@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.0.tgz#5c0a3f29c8ccffbbb1ec941dcec09d71fa32f36a" - integrity sha1-XAo/KcjM/7ux7JQdzsCdcfoy82o= + version "1.1.1" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.1.tgz#85aa26d3daeeeedf80dc5984af95cc5ca5cad9f1" + integrity sha1-haom09ru7t+A3FmEr5XMXKXK2fE= normalize-path@^2.0.1: version "2.1.1" @@ -302,13 +275,6 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - pad-right@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774" @@ -326,31 +292,27 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= prompts@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-1.2.0.tgz#598f7722032fb6c399beb24533129d00604c7007" - integrity sha512-g+I6Cer6EefDTawQhGHpdX98nhD7KQrRqyRgKCb+Sc+GG4P64EWRe5DZE402ZNkwrItf97Asf0L1z0g3waOgAA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-1.2.1.tgz#7fd4116a458d6a62761e3ccb1432d7bbd8b2cb29" + integrity sha512-GE33SMMVO1ISfnq3i6cE+WYK/tLxRWtZiRkl5vdg0KR0owOCPFOsq8BuFajFbW7b2bMHb8krXaQHOpZyUEuvmA== dependencies: - kleur "^2.0.1" + kleur "^3.0.0" sisteransi "^1.0.0" -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - integrity sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how== +randomatic@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" + integrity sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" regex-cache@^0.4.2: version "0.4.4" @@ -365,9 +327,9 @@ remove-trailing-separator@^1.0.1: integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo= + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== repeat-string@^1.5.2: version "1.6.1" @@ -390,9 +352,9 @@ rollup-pluginutils@^2.3.1: micromatch "^2.3.11" rollup@^0.67.0: - version "0.67.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.67.0.tgz#16d4f259c55224dded6408e7666b7731500797a3" - integrity sha512-p34buXxArhwv9ieTdHvdhdo65Cbig68s/Z8llbZuiX5e+3zCqnBF02Ck9IH0tECrmvvrJVMws32Ry84hTnS1Tw== + version "0.67.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.67.3.tgz#55475b1b62c43220c3b4bd7edc5846233932f50b" + integrity sha512-TyNQCz97rKuVVbsKUTXfwIjV7UljWyTVd7cTMuE+aqlQ7WJslkYF5QaYGjMLR2BlQtUOO5CAxSVnpQ55iYp5jg== dependencies: "@types/estree" "0.0.39" "@types/node" "*" @@ -410,27 +372,35 @@ sisteransi@^1.0.0: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c" integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ== +source-map-support@^0.5.9: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" + integrity sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + sourcemap-codec@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz#c8fd92d91889e902a07aee392bdd2c5863958ba2" - integrity sha512-hX1eNBNuilj8yfFnECh0DzLgwKpBLMIvmhgEhixXNui8lMLBInTI8Kyxt++RwJnMNu7cAUo635L2+N1TxMJCzA== + version "1.4.4" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f" + integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg== -svelte@1: +"svelte-1@npm:svelte@1": version "1.64.1" resolved "https://registry.yarnpkg.com/svelte/-/svelte-1.64.1.tgz#03c97e204e0277c1430f429a6755ca425852e5de" integrity sha512-RsEAcVYF90Bq5y4Lgg56LyKiuxiyDTU+5TG2GM0ppa50z446de0vQgnA3eQTrgecPdRR89ZIqNqN1cgdAdO7wQ== +"svelte-2@npm:svelte@2", svelte@^2.15.2: + version "2.15.3" + resolved "https://registry.yarnpkg.com/svelte/-/svelte-2.15.3.tgz#d9ba5b48eae30f5501b3266c563add2399c67b40" + integrity sha512-0bKpXppM/YlPZ8F0mREaUYpE8Te11RvG62ttFr+n1U3G2qdr3cZzuXCnQMcVZukwAUJAjRM55OSH7FLmXnPGRA== + tape-modern@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/tape-modern/-/tape-modern-1.1.1.tgz#32189f709740672dc4ca965489208caba983986e" integrity sha512-sb1PNwSlphD2t5z9diUO7dd+r25sfdw94wD3yyi1Dcmu7oJ6wKMGdZ/pB6OZ0jqm0oAEESL4iOHeiCqnHhc1dg== - -turbocolor@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/turbocolor/-/turbocolor-2.6.1.tgz#1b47dcc0e0e5171f57d954351fb80a5088a8a921" - integrity sha512-0pTvPfKBIasx7C4bEorJY9I3SNbkyQrFeRxK8iMoarEHZLQLOhPungv9t/7SSfKLJdErhv4dC2GKmaNGURnk4A== - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=