feat: migrate `svelte:self` ()

* feat: migrate `svelte:self`

* chore: regenerate types

* fix: special case `<svelte:self></svelte:self>`

* chore: add special case to tests

* chore: add no filename test

* chore: better migration task message

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>

* chore: make filename an options object to futureproof it

* chore: simplify open tag `svelte:self`

* chore: simplify migration comment test

* chore: generate types

* chore: apply smart suggestion

* chore: changeset

---------

Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
pull/13515/head
Paolo Ricciuti 6 months ago committed by GitHub
parent 687d9dbef2
commit 14ecedf33b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
feat: support migrating `svelte:self`

@ -27,9 +27,10 @@ const style_placeholder = '/*$$__STYLE_CONTENT__$$*/';
* May throw an error if the code is too complex to migrate automatically.
*
* @param {string} source
* @param {{filename?: string}} [options]
* @returns {{ code: string; }}
*/
export function migrate(source) {
export function migrate(source, { filename } = {}) {
try {
// Blank CSS, could contain SCSS or similar that needs a preprocessor.
// Since we don't care about CSS in this migration, we'll just ignore it.
@ -41,7 +42,7 @@ export function migrate(source) {
});
reset_warning_filter(() => false);
reset(source, { filename: 'migrate.svelte' });
reset(source, { filename: filename ?? 'migrate.svelte' });
let parsed = parse(source);
@ -68,6 +69,7 @@ export function migrate(source) {
let state = {
scope: analysis.instance.scope,
analysis,
filename,
str,
indent,
props: [],
@ -90,12 +92,14 @@ export function migrate(source) {
createBubbler: analysis.root.unique('createBubbler').name,
bubble: analysis.root.unique('bubble').name,
passive: analysis.root.unique('passive').name,
nonpassive: analysis.root.unique('nonpassive').name
nonpassive: analysis.root.unique('nonpassive').name,
svelte_self: analysis.root.unique('SvelteSelf').name
},
legacy_imports: new Set(),
script_insertions: new Set(),
derived_components: new Map(),
derived_labeled_statements: new Set()
derived_labeled_statements: new Set(),
has_svelte_self: false
};
if (parsed.module) {
@ -122,12 +126,21 @@ export function migrate(source) {
state.script_insertions.size > 0 ||
state.props.length > 0 ||
analysis.uses_rest_props ||
analysis.uses_props;
analysis.uses_props ||
state.has_svelte_self;
if (!parsed.instance && need_script) {
str.appendRight(0, '<script>');
}
if (state.has_svelte_self && filename) {
const file = filename.split('/').pop();
str.appendRight(
insertion_point,
`\n${indent}import ${state.names.svelte_self} from './${file}';`
);
}
const specifiers = [...state.legacy_imports].map((imported) => {
const local = state.names[imported];
return imported === local ? imported : `${imported} as ${local}`;
@ -298,6 +311,7 @@ export function migrate(source) {
* scope: Scope;
* str: MagicString;
* analysis: ComponentAnalysis;
* filename?: string;
* indent: string;
* props: Array<{ local: string; exported: string; init: string; bindable: boolean; slot_name?: string; optional: boolean; type: string; comment?: string; type_only?: boolean; needs_refine_type?: boolean; }>;
* props_insertion_point: number;
@ -306,8 +320,9 @@ export function migrate(source) {
* names: Record<string, string>;
* legacy_imports: Set<string>;
* script_insertions: Set<string>;
* derived_components: Map<string, string>,
* derived_labeled_statements: Set<LabeledStatement>
* derived_components: Map<string, string>;
* derived_labeled_statements: Set<LabeledStatement>;
* has_svelte_self: boolean;
* }} State
*/
@ -729,9 +744,44 @@ const template = {
}
next();
},
SvelteSelf(node, { state, next }) {
const source = state.str.original.substring(node.start, node.end);
if (!state.filename) {
const indent = guess_indent(source);
state.str.prependRight(
node.start,
`<!-- @migration-task: svelte:self is deprecated, import this Svelte file into itself instead -->\n${indent}`
);
next();
return;
}
// overwrite the open tag
state.str.overwrite(
node.start + 1,
node.start + 1 + 'svelte:self'.length,
`${state.names.svelte_self}`
);
// if it has a fragment we need to overwrite the closing tag too
if (node.fragment.nodes.length > 0) {
state.str.overwrite(
state.str.original.lastIndexOf('<', node.end) + 2,
node.end - 1,
`${state.names.svelte_self}`
);
} else if (!source.endsWith('/>')) {
// special case for case `<svelte:self></svelte:self>` it has no fragment but
// we still need to overwrite the end tag
state.str.overwrite(
node.start + source.lastIndexOf('</', node.end) + 2,
node.end - 1,
`${state.names.svelte_self}`
);
}
state.has_svelte_self = true;
next();
},
SvelteElement(node, { state, path, next }) {
migrate_slot_usage(node, path, state);
if (node.tag.type === 'Literal') {
let is_static = true;

@ -0,0 +1,19 @@
<script>
let SvelteSelf;
</script>
{#if false}
<svelte:self />
<svelte:self with_attributes/>
<svelte:self count={count+1}/>
<svelte:self>
child
</svelte:self>
<svelte:self count={count+1}>
child
</svelte:self>
<svelte:self count={$$props.count} >
child
</svelte:self>
<svelte:self></svelte:self>
{/if}

@ -0,0 +1,22 @@
<script>
import SvelteSelf_1 from './output.svelte';
/** @type {Record<string, any>} */
let { ...props } = $props();
let SvelteSelf;
</script>
{#if false}
<SvelteSelf_1 />
<SvelteSelf_1 with_attributes/>
<SvelteSelf_1 count={count+1}/>
<SvelteSelf_1>
child
</SvelteSelf_1>
<SvelteSelf_1 count={count+1}>
child
</SvelteSelf_1>
<SvelteSelf_1 count={props.count} >
child
</SvelteSelf_1>
<SvelteSelf_1></SvelteSelf_1>
{/if}

@ -0,0 +1,5 @@
import { test } from '../../test';
export default test({
skip_filename: true
});

@ -0,0 +1,4 @@
{#if false}
<!-- @migration-task: svelte:self is deprecated, import this Svelte file into itself instead -->
<svelte:self />
{/if}

@ -0,0 +1,15 @@
{#if false}
<svelte:self />
<svelte:self with_attributes/>
<svelte:self count={count+1}/>
<svelte:self>
child
</svelte:self>
<svelte:self count={count+1}>
child
</svelte:self>
<svelte:self count={$$props.count} >
child
</svelte:self>
<svelte:self></svelte:self>
{/if}

@ -0,0 +1,21 @@
<script>
import SvelteSelf from './output.svelte';
/** @type {Record<string, any>} */
let { ...props } = $props();
</script>
{#if false}
<SvelteSelf />
<SvelteSelf with_attributes/>
<SvelteSelf count={count+1}/>
<SvelteSelf>
child
</SvelteSelf>
<SvelteSelf count={count+1}>
child
</SvelteSelf>
<SvelteSelf count={props.count} >
child
</SvelteSelf>
<SvelteSelf></SvelteSelf>
{/if}

@ -4,7 +4,9 @@ import { migrate } from 'svelte/compiler';
import { try_read_file } from '../helpers.js';
import { suite, type BaseTest } from '../suite.js';
interface ParserTest extends BaseTest {}
interface ParserTest extends BaseTest {
skip_filename?: boolean;
}
const { test, run } = suite<ParserTest>(async (config, cwd) => {
const input = fs
@ -12,7 +14,9 @@ const { test, run } = suite<ParserTest>(async (config, cwd) => {
.replace(/\s+$/, '')
.replace(/\r/g, '');
const actual = migrate(input).code;
const actual = migrate(input, {
filename: config.skip_filename ? undefined : `${cwd}/output.svelte`
}).code;
// run `UPDATE_SNAPSHOTS=true pnpm test migrate` to update parser tests
if (process.env.UPDATE_SNAPSHOTS || !fs.existsSync(`${cwd}/output.svelte`)) {

@ -1264,7 +1264,9 @@ declare module 'svelte/compiler' {
* May throw an error if the code is too complex to migrate automatically.
*
* */
export function migrate(source: string): {
export function migrate(source: string, { filename }?: {
filename?: string;
} | undefined): {
code: string;
};
namespace Css {

@ -80,7 +80,8 @@ export default class Compiler {
this.worker.postMessage({
id,
type: 'migrate',
source: file.source
source: file.source,
filename: `${file.name}.${file.type}`
});
});
}

@ -133,9 +133,9 @@ function compile({ id, source, options, return_ast }) {
}
/** @param {import("../workers").MigrateMessageData} param0 */
function migrate({ id, source }) {
function migrate({ id, source, filename }) {
try {
const result = svelte.migrate(source);
const result = svelte.migrate(source, { filename });
return {
id,

Loading…
Cancel
Save