From 1c5f9b98ad0bd6ab273b249e793eb1a773ebec40 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 20 Apr 2023 11:42:59 +0200 Subject: [PATCH 1/5] feat: add Svelte 4 migration --- .changeset/lucky-coins-hunt.md | 5 + packages/migrate/migrations/svelte-4/index.js | 73 +++++++ .../migrate/migrations/svelte-4/migrate.js | 179 ++++++++++++++++++ .../migrations/svelte-4/migrate.spec.js | 120 ++++++++++++ packages/migrate/package.json | 3 +- pnpm-lock.yaml | 60 ++++-- 6 files changed, 427 insertions(+), 13 deletions(-) create mode 100644 .changeset/lucky-coins-hunt.md create mode 100644 packages/migrate/migrations/svelte-4/index.js create mode 100644 packages/migrate/migrations/svelte-4/migrate.js create mode 100644 packages/migrate/migrations/svelte-4/migrate.spec.js diff --git a/.changeset/lucky-coins-hunt.md b/.changeset/lucky-coins-hunt.md new file mode 100644 index 000000000000..78d11ddc8907 --- /dev/null +++ b/.changeset/lucky-coins-hunt.md @@ -0,0 +1,5 @@ +--- +'svelte-migrate': minor +--- + +feat: add Svelte 4 migration diff --git a/packages/migrate/migrations/svelte-4/index.js b/packages/migrate/migrations/svelte-4/index.js new file mode 100644 index 000000000000..ef9269cc4679 --- /dev/null +++ b/packages/migrate/migrations/svelte-4/index.js @@ -0,0 +1,73 @@ +import colors from 'kleur'; +import fs from 'node:fs'; +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; +import prompts from 'prompts'; +import glob from 'tiny-glob/sync.js'; +import { bail, check_git } from '../../utils.js'; +import { update_js_file, update_svelte_file } from './migrate.js'; + +export async function migrate() { + if (!fs.existsSync('package.json')) { + bail('Please re-run this script in a directory with a package.json'); + } + + console.log(colors.bold().yellow('\nThis will update files in the current directory\n')); + + const use_git = check_git(); + + const response = await prompts({ + type: 'confirm', + name: 'value', + message: 'Continue?', + initial: false + }); + + if (!response.value) { + process.exit(1); + } + + const { default: config } = fs.existsSync('svelte.config.js') + ? await import(pathToFileURL(path.resolve('svelte.config.js')).href) + : { default: {} }; + + /** @type {string[]} */ + const svelte_extensions = config.extensions ?? ['.svelte']; + const extensions = [...svelte_extensions, '.ts', '.js']; + // TODO read tsconfig/jsconfig if available? src/** will be good for 99% of cases + const files = glob(`src/**`, { filesOnly: true, dot: true }).map((file) => + file.replace(/\\/g, '/') + ); + + for (const file of files) { + if (extensions.some((ext) => file.endsWith(ext))) { + if (svelte_extensions.some((ext) => file.endsWith(ext))) { + update_svelte_file(file); + } else { + update_js_file(file); + } + } + } + + console.log(colors.bold().green('✔ Your project has been migrated')); + + console.log('\nRecommended next steps:\n'); + + const cyan = colors.bold().cyan; + + const tasks = [ + use_git && cyan('git commit -m "migration to Svelte 4"'), + `Review the migration guide at TODO`, + `Read the updated docs at https://svelte.dev/docs` + ].filter(Boolean); + + tasks.forEach((task, i) => { + console.log(` ${i + 1}: ${task}`); + }); + + console.log(''); + + if (use_git) { + console.log(`Run ${cyan('git diff')} to review changes.\n`); + } +} diff --git a/packages/migrate/migrations/svelte-4/migrate.js b/packages/migrate/migrations/svelte-4/migrate.js new file mode 100644 index 000000000000..d11e8c2488e3 --- /dev/null +++ b/packages/migrate/migrations/svelte-4/migrate.js @@ -0,0 +1,179 @@ +import fs from 'node:fs'; +import { Project, SourceFile, ts, Node } from 'ts-morph'; + +/** @param {string} file_path */ +export function update_svelte_file(file_path) { + const content = fs.readFileSync(file_path, 'utf-8'); + const updated = content.replace( + /([^]+?)<\/script>(\n*)/g, + (_match, attrs, contents, whitespace) => { + return `${transform_code(contents)}${whitespace}`; + } + ); + fs.writeFileSync(file_path, updated, 'utf-8'); +} + +/** @param {string} file_path */ +export function update_js_file(file_path) { + const content = fs.readFileSync(file_path, 'utf-8'); + const updated = transform_code(content); + fs.writeFileSync(file_path, updated, 'utf-8'); +} + +/** @param {string} code */ +export function transform_code(code) { + const project = new Project({ useInMemoryFileSystem: true }); + const source = project.createSourceFile('svelte.ts', code); + update_imports(source); + update_typeof_svelte_component(source); + update_action_types(source); + update_action_return_types(source); + return source.getFullText(); +} + +// -> + +/** + * Action -> Action + * @param {SourceFile} source + */ +function update_action_types(source) { + const imports = get_imports(source, 'svelte/action', 'Action'); + for (const namedImport of imports) { + const identifiers = find_identifiers(source, namedImport.getAliasNode()?.getText() ?? 'Action'); + for (const id of identifiers) { + const parent = id.getParent(); + if (Node.isTypeReference(parent)) { + const type_args = parent.getTypeArguments(); + if (type_args.length === 1) { + parent.addTypeArgument('any'); + } else if (type_args.length === 0) { + parent.addTypeArgument('HTMLElement'); + parent.addTypeArgument('any'); + } + } + } + } +} + +/** + * ActionReturn -> ActionReturn + * @param {SourceFile} source + */ +function update_action_return_types(source) { + const imports = get_imports(source, 'svelte/action', 'ActionReturn'); + for (const namedImport of imports) { + const identifiers = find_identifiers( + source, + namedImport.getAliasNode()?.getText() ?? 'ActionReturn' + ); + for (const id of identifiers) { + const parent = id.getParent(); + if (Node.isTypeReference(parent)) { + const type_args = parent.getTypeArguments(); + if (type_args.length === 0) { + parent.addTypeArgument('any'); + } + } + } + } +} + +/** + * SvelteComponentTyped -> SvelteComponent + * @param {SourceFile} source + */ +function update_imports(source) { + const identifiers = find_identifiers(source, 'SvelteComponent'); + const can_rename = identifiers.every((id) => { + const parent = id.getParent(); + return ( + (Node.isImportSpecifier(parent) && + !parent.getAliasNode() && + parent.getParent().getParent().getParent().getModuleSpecifier().getText() === 'svelte') || + !is_declaration(parent) + ); + }); + + const imports = get_imports(source, 'svelte', 'SvelteComponentTyped'); + for (const namedImport of imports) { + if (can_rename) { + namedImport.renameAlias('SvelteComponent'); + if ( + namedImport + .getParent() + .getElements() + .some((e) => !e.getAliasNode() && e.getNameNode().getText() === 'SvelteComponent') + ) { + namedImport.remove(); + } else { + namedImport.setName('SvelteComponent'); + namedImport.removeAlias(); + } + } else { + namedImport.renameAlias('SvelteComponentTyped'); + namedImport.setName('SvelteComponent'); + } + } +} + +/** + * typeof SvelteComponent -> typeof SvelteComponent + * @param {SourceFile} source + */ +function update_typeof_svelte_component(source) { + const imports = get_imports(source, 'svelte', 'SvelteComponent'); + + for (const type of imports) { + if (type) { + const name = type.getAliasNode() ?? type.getNameNode(); + name.findReferencesAsNodes().forEach((ref) => { + const parent = ref.getParent(); + if (parent && Node.isTypeQuery(parent)) { + const id = parent.getFirstChildByKind(ts.SyntaxKind.Identifier); + if (id?.getText() === name.getText()) { + const typeArguments = parent.getTypeArguments(); + if (typeArguments.length === 0) { + parent.addTypeArgument('any'); + } + } + } + }); + } + } +} + +/** + * @param {SourceFile} source + * @param {string} from + * @param {string} name + */ +function get_imports(source, from, name) { + return source + .getImportDeclarations() + .filter((i) => i.getModuleSpecifierValue() === from) + .flatMap((i) => i.getNamedImports()) + .filter((i) => i.getName() === name); +} + +/** + * @param {SourceFile} source + * @param {string} name + */ +function find_identifiers(source, name) { + return source.getDescendantsOfKind(ts.SyntaxKind.Identifier).filter((i) => i.getText() === name); +} + +/** + * Does not include imports + * @param {Node} node + */ +function is_declaration(node) { + return ( + Node.isVariableDeclaration(node) || + Node.isFunctionDeclaration(node) || + Node.isClassDeclaration(node) || + Node.isTypeAliasDeclaration(node) || + Node.isInterfaceDeclaration(node) + ); +} diff --git a/packages/migrate/migrations/svelte-4/migrate.spec.js b/packages/migrate/migrations/svelte-4/migrate.spec.js new file mode 100644 index 000000000000..c04ec8b43e63 --- /dev/null +++ b/packages/migrate/migrations/svelte-4/migrate.spec.js @@ -0,0 +1,120 @@ +import { test } from 'uvu'; +import * as assert from 'uvu/assert'; +import { transform_code } from './migrate.js'; + +test('Updates SvelteComponentTyped #1', () => { + const result = transform_code( + `import { SvelteComponentTyped } from 'svelte'; + +export class Foo extends SvelteComponentTyped<{}> {} + +const bar: SvelteComponentTyped = null;` + ); + assert.equal( + result, + `import { SvelteComponent } from 'svelte'; + +export class Foo extends SvelteComponent<{}> {} + +const bar: SvelteComponent = null;` + ); +}); + +test('Updates SvelteComponentTyped #2', () => { + const result = transform_code( + `import { SvelteComponentTyped, SvelteComponent } from 'svelte'; + +export class Foo extends SvelteComponentTyped<{}> {} + +const bar: SvelteComponentTyped = null; +const baz: SvelteComponent = null;` + ); + assert.equal( + result, + `import { SvelteComponent } from 'svelte'; + +export class Foo extends SvelteComponent<{}> {} + +const bar: SvelteComponent = null; +const baz: SvelteComponent = null;` + ); +}); + +test('Updates SvelteComponentTyped #3', () => { + const result = transform_code( + `import { SvelteComponentTyped } from 'svelte'; + +interface SvelteComponent {} + +export class Foo extends SvelteComponentTyped<{}> {} + +const bar: SvelteComponentTyped = null; +const baz: SvelteComponent = null;` + ); + assert.equal( + result, + `import { SvelteComponent as SvelteComponentTyped } from 'svelte'; + +interface SvelteComponent {} + +export class Foo extends SvelteComponentTyped<{}> {} + +const bar: SvelteComponentTyped = null; +const baz: SvelteComponent = null;` + ); +}); + +test('Updates typeof SvelteComponent', () => { + const result = transform_code( + `import { SvelteComponent } from 'svelte'; + import { SvelteComponent as C } from 'svelte'; + + const a: typeof SvelteComponent = null; + function b(c: typeof SvelteComponent) {} + const c: typeof SvelteComponent = null; + const d: typeof C = null; + ` + ); + assert.equal( + result, + `import { SvelteComponent } from 'svelte'; + import { SvelteComponent as C } from 'svelte'; + + const a: typeof SvelteComponent = null; + function b(c: typeof SvelteComponent) {} + const c: typeof SvelteComponent = null; + const d: typeof C = null; + ` + ); +}); + +test('Updates Action and ActionReturn', () => { + const result = transform_code( + `import { Action, ActionReturn } from 'svelte/action'; + + const a: Action = () => {}; + const b: Action = () => {}; + const c: Action = () => {}; + const d: Action = () => {}; + const e: ActionReturn = () => {}; + const f: ActionReturn = () => {}; + const g: ActionReturn = () => {}; + ` + ); + assert.equal( + result, + + `import { Action, ActionReturn } from 'svelte/action'; + + const a: Action = () => {}; + const b: Action = () => {}; + const c: Action = () => {}; + const d: Action = () => {}; + const e: ActionReturn = () => {}; + const f: ActionReturn = () => {}; + const g: ActionReturn = () => {}; + ` + ); +}); + +test.run(); diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 39e238638ed6..498fc5bec34d 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -28,7 +28,8 @@ "magic-string": "^0.30.0", "prompts": "^2.4.2", "tiny-glob": "^0.2.9", - "typescript": "^4.9.4" + "ts-morph": "^18.0.0", + "typescript": "^5.0.4" }, "devDependencies": { "@types/prompts": "^2.4.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a3d1b62cf4b..7584e4e67dfb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -895,9 +895,12 @@ importers: tiny-glob: specifier: ^0.2.9 version: 0.2.9 + ts-morph: + specifier: ^18.0.0 + version: 18.0.0 typescript: - specifier: ^4.9.4 - version: 4.9.4 + specifier: ^5.0.4 + version: 5.0.4 devDependencies: '@types/prompts': specifier: ^2.4.1 @@ -1765,12 +1768,10 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - dev: true /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} @@ -1778,7 +1779,6 @@ packages: dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 - dev: true /@playwright/test@1.29.2: resolution: {integrity: sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==} @@ -1893,6 +1893,15 @@ packages: - encoding dev: true + /@ts-morph/common@0.19.0: + resolution: {integrity: sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==} + dependencies: + fast-glob: 3.2.12 + minimatch: 7.4.6 + mkdirp: 2.1.6 + path-browserify: 1.0.1 + dev: false + /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: @@ -2591,6 +2600,10 @@ packages: engines: {node: '>=0.8'} dev: true + /code-block-writer@12.0.0: + resolution: {integrity: sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==} + dev: false + /code-point-at@1.1.0: resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} engines: {node: '>=0.10.0'} @@ -3233,7 +3246,6 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 - dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -3247,7 +3259,6 @@ packages: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 - dev: true /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -4104,7 +4115,6 @@ packages: /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} @@ -4144,6 +4154,13 @@ packages: dependencies: brace-expansion: 2.0.1 + /minimatch@7.4.6: + resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + /minimatch@9.0.0: resolution: {integrity: sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==} engines: {node: '>=16 || 14 >=14.17'} @@ -4206,6 +4223,12 @@ packages: hasBin: true dev: false + /mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + dev: false + /mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -4476,6 +4499,10 @@ packages: tslib: 2.4.1 dev: false + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: false + /path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -4684,7 +4711,6 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true /quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} @@ -4847,7 +4873,6 @@ packages: /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true /rework@1.0.1: resolution: {integrity: sha512-eEjL8FdkdsxApd0yWVZgBGzfCQiT8yqSc2H1p4jpZpQdtz7ohETiDMoje5PlM8I9WgkqkreVxFUKYOiJdVWDXw==} @@ -4901,7 +4926,6 @@ packages: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - dev: true /sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} @@ -5020,7 +5044,7 @@ packages: '@typescript/twoslash': 3.1.0 '@typescript/vfs': 1.3.4 shiki: 0.10.1 - typescript: 5.0.2 + typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true @@ -5541,6 +5565,13 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /ts-morph@18.0.0: + resolution: {integrity: sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==} + dependencies: + '@ts-morph/common': 0.19.0 + code-block-writer: 12.0.0 + dev: false + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true @@ -5617,6 +5648,11 @@ packages: hasBin: true dev: true + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true + /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} From 65fff4a1a9431cb73339af0478bb25542923c118 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Mon, 8 May 2023 15:38:56 +0200 Subject: [PATCH 2/5] custom element migration --- .../migrate/migrations/svelte-4/migrate.js | 17 +++++++- .../migrations/svelte-4/migrate.spec.js | 42 ++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/packages/migrate/migrations/svelte-4/migrate.js b/packages/migrate/migrations/svelte-4/migrate.js index d11e8c2488e3..48a834e69ceb 100644 --- a/packages/migrate/migrations/svelte-4/migrate.js +++ b/packages/migrate/migrations/svelte-4/migrate.js @@ -10,7 +10,7 @@ export function update_svelte_file(file_path) { return `${transform_code(contents)}${whitespace}`; } ); - fs.writeFileSync(file_path, updated, 'utf-8'); + fs.writeFileSync(file_path, transform_svelte_code(updated), 'utf-8'); } /** @param {string} file_path */ @@ -31,7 +31,20 @@ export function transform_code(code) { return source.getFullText(); } -// -> +/** @param {string} code */ +export function transform_svelte_code(code) { + return update_svelte_options(code); +} + +/** + * -> + * @param {string} code + */ +function update_svelte_options(code) { + return code.replace(//, (match) => { + return match.replace('tag=', 'customElement='); + }); +} /** * Action -> Action diff --git a/packages/migrate/migrations/svelte-4/migrate.spec.js b/packages/migrate/migrations/svelte-4/migrate.spec.js index c04ec8b43e63..7399c671ffe6 100644 --- a/packages/migrate/migrations/svelte-4/migrate.spec.js +++ b/packages/migrate/migrations/svelte-4/migrate.spec.js @@ -1,6 +1,6 @@ import { test } from 'uvu'; import * as assert from 'uvu/assert'; -import { transform_code } from './migrate.js'; +import { transform_code, transform_svelte_code } from './migrate.js'; test('Updates SvelteComponentTyped #1', () => { const result = transform_code( @@ -117,4 +117,44 @@ test('Updates Action and ActionReturn', () => { ); }); +test('Updates svelte:options #1', () => { + const result = transform_svelte_code( + ` + +
hi
` + ); + assert.equal( + result, + ` + +
hi
` + ); +}); + +test('Updates svelte:options #2', () => { + const result = transform_svelte_code( + ` + + + +
hi
` + ); + assert.equal( + result, + ` + + + +
hi
` + ); +}); + test.run(); From 521910c54f2fbe203487f5c310e568f22fb88d6d Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 26 May 2023 23:06:32 +0200 Subject: [PATCH 3/5] migrate transition global --- .../migrate/migrations/svelte-4/migrate.js | 10 ++++- .../migrations/svelte-4/migrate.spec.js | 43 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/migrate/migrations/svelte-4/migrate.js b/packages/migrate/migrations/svelte-4/migrate.js index 48a834e69ceb..0cf35b18c32c 100644 --- a/packages/migrate/migrations/svelte-4/migrate.js +++ b/packages/migrate/migrations/svelte-4/migrate.js @@ -33,7 +33,7 @@ export function transform_code(code) { /** @param {string} code */ export function transform_svelte_code(code) { - return update_svelte_options(code); + return update_transitions(update_svelte_options(code)); } /** @@ -46,6 +46,14 @@ function update_svelte_options(code) { }); } +/** + * transition/in/out:x -> transition/in/out:x|global + * @param {string} code + */ +function update_transitions(code) { + return code.replace(/(\s)(transition:|in:|out:)(\w+)(?=[\s>=])/g, '$1$2$3|global'); +} + /** * Action -> Action * @param {SourceFile} source diff --git a/packages/migrate/migrations/svelte-4/migrate.spec.js b/packages/migrate/migrations/svelte-4/migrate.spec.js index 7399c671ffe6..d2a01a07e517 100644 --- a/packages/migrate/migrations/svelte-4/migrate.spec.js +++ b/packages/migrate/migrations/svelte-4/migrate.spec.js @@ -1,5 +1,4 @@ -import { test } from 'uvu'; -import * as assert from 'uvu/assert'; +import { assert, test } from 'vitest'; import { transform_code, transform_svelte_code } from './migrate.js'; test('Updates SvelteComponentTyped #1', () => { @@ -157,4 +156,42 @@ test('Updates svelte:options #2', () => { ); }); -test.run(); +test('Updates transitions', () => { + const result = transform_svelte_code( + `
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ ` + ); + assert.equal( + result, + `
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ ` + ); +}); From 295ef7a1465c9cb813702494894dd50d462d86ba Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 26 May 2023 23:13:23 +0200 Subject: [PATCH 4/5] lint --- packages/migrate/migrations/svelte-4/index.js | 6 +++--- packages/migrate/migrations/svelte-4/migrate.js | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/migrate/migrations/svelte-4/index.js b/packages/migrate/migrations/svelte-4/index.js index ef9269cc4679..e33072618b82 100644 --- a/packages/migrate/migrations/svelte-4/index.js +++ b/packages/migrate/migrations/svelte-4/index.js @@ -35,7 +35,7 @@ export async function migrate() { const svelte_extensions = config.extensions ?? ['.svelte']; const extensions = [...svelte_extensions, '.ts', '.js']; // TODO read tsconfig/jsconfig if available? src/** will be good for 99% of cases - const files = glob(`src/**`, { filesOnly: true, dot: true }).map((file) => + const files = glob('src/**', { filesOnly: true, dot: true }).map((file) => file.replace(/\\/g, '/') ); @@ -57,8 +57,8 @@ export async function migrate() { const tasks = [ use_git && cyan('git commit -m "migration to Svelte 4"'), - `Review the migration guide at TODO`, - `Read the updated docs at https://svelte.dev/docs` + 'Review the migration guide at TODO', + 'Read the updated docs at https://svelte.dev/docs' ].filter(Boolean); tasks.forEach((task, i) => { diff --git a/packages/migrate/migrations/svelte-4/migrate.js b/packages/migrate/migrations/svelte-4/migrate.js index 0cf35b18c32c..5609167f230e 100644 --- a/packages/migrate/migrations/svelte-4/migrate.js +++ b/packages/migrate/migrations/svelte-4/migrate.js @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import { Project, SourceFile, ts, Node } from 'ts-morph'; +import { Project, ts, Node } from 'ts-morph'; /** @param {string} file_path */ export function update_svelte_file(file_path) { @@ -56,7 +56,7 @@ function update_transitions(code) { /** * Action -> Action - * @param {SourceFile} source + * @param {import('ts-morph').SourceFile} source */ function update_action_types(source) { const imports = get_imports(source, 'svelte/action', 'Action'); @@ -79,7 +79,7 @@ function update_action_types(source) { /** * ActionReturn -> ActionReturn - * @param {SourceFile} source + * @param {import('ts-morph').SourceFile} source */ function update_action_return_types(source) { const imports = get_imports(source, 'svelte/action', 'ActionReturn'); @@ -102,7 +102,7 @@ function update_action_return_types(source) { /** * SvelteComponentTyped -> SvelteComponent - * @param {SourceFile} source + * @param {import('ts-morph').SourceFile} source */ function update_imports(source) { const identifiers = find_identifiers(source, 'SvelteComponent'); @@ -140,7 +140,7 @@ function update_imports(source) { /** * typeof SvelteComponent -> typeof SvelteComponent - * @param {SourceFile} source + * @param {import('ts-morph').SourceFile} source */ function update_typeof_svelte_component(source) { const imports = get_imports(source, 'svelte', 'SvelteComponent'); @@ -165,7 +165,7 @@ function update_typeof_svelte_component(source) { } /** - * @param {SourceFile} source + * @param {import('ts-morph').SourceFile} source * @param {string} from * @param {string} name */ @@ -178,7 +178,7 @@ function get_imports(source, from, name) { } /** - * @param {SourceFile} source + * @param {import('ts-morph').SourceFile} source * @param {string} name */ function find_identifiers(source, name) { From a134b952ae103e2c97ac3ec1d4cd1dd4246e54ca Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Tue, 30 May 2023 10:08:33 -0700 Subject: [PATCH 5/5] comment out extensions support --- packages/migrate/migrations/svelte-4/index.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/migrate/migrations/svelte-4/index.js b/packages/migrate/migrations/svelte-4/index.js index e33072618b82..a1acfa7c34a1 100644 --- a/packages/migrate/migrations/svelte-4/index.js +++ b/packages/migrate/migrations/svelte-4/index.js @@ -1,7 +1,5 @@ import colors from 'kleur'; import fs from 'node:fs'; -import path from 'node:path'; -import { pathToFileURL } from 'node:url'; import prompts from 'prompts'; import glob from 'tiny-glob/sync.js'; import { bail, check_git } from '../../utils.js'; @@ -27,12 +25,12 @@ export async function migrate() { process.exit(1); } - const { default: config } = fs.existsSync('svelte.config.js') - ? await import(pathToFileURL(path.resolve('svelte.config.js')).href) - : { default: {} }; + // const { default: config } = fs.existsSync('svelte.config.js') + // ? await import(pathToFileURL(path.resolve('svelte.config.js')).href) + // : { default: {} }; /** @type {string[]} */ - const svelte_extensions = config.extensions ?? ['.svelte']; + const svelte_extensions = /* config.extensions ?? - disabled because it would break .svx */ ['.svelte']; const extensions = [...svelte_extensions, '.ts', '.js']; // TODO read tsconfig/jsconfig if available? src/** will be good for 99% of cases const files = glob('src/**', { filesOnly: true, dot: true }).map((file) =>