feat: migrate `svelte:self`

pull/13504/head
paoloricciuti 2 years ago
parent aa3f002465
commit fbe46ecdbc

@ -10,7 +10,7 @@ import { regex_valid_component_name } from '../phases/1-parse/state/element.js';
import { analyze_component } from '../phases/2-analyze/index.js'; import { analyze_component } from '../phases/2-analyze/index.js';
import { get_rune } from '../phases/scope.js'; import { get_rune } from '../phases/scope.js';
import { reset, reset_warning_filter } from '../state.js'; import { reset, reset_warning_filter } from '../state.js';
import { extract_identifiers, extract_all_identifiers_from_expression } from '../utils/ast.js'; import { extract_all_identifiers_from_expression, extract_identifiers } from '../utils/ast.js';
import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js'; import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js';
import { validate_component_options } from '../validate-options.js'; import { validate_component_options } from '../validate-options.js';
import { is_svg, is_void } from '../../utils.js'; import { is_svg, is_void } from '../../utils.js';
@ -23,9 +23,10 @@ const style_placeholder = '/*$$__STYLE_CONTENT__$$*/';
* May throw an error if the code is too complex to migrate automatically. * May throw an error if the code is too complex to migrate automatically.
* *
* @param {string} source * @param {string} source
* @param {string} [filename]
* @returns {{ code: string; }} * @returns {{ code: string; }}
*/ */
export function migrate(source) { export function migrate(source, filename) {
try { try {
// Blank CSS, could contain SCSS or similar that needs a preprocessor. // 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. // Since we don't care about CSS in this migration, we'll just ignore it.
@ -37,7 +38,7 @@ export function migrate(source) {
}); });
reset_warning_filter(() => false); reset_warning_filter(() => false);
reset(source, { filename: 'migrate.svelte' }); reset(source, { filename: filename ?? 'migrate.svelte' });
let parsed = parse(source); let parsed = parse(source);
@ -64,6 +65,7 @@ export function migrate(source) {
let state = { let state = {
scope: analysis.instance.scope, scope: analysis.instance.scope,
analysis, analysis,
filename,
str, str,
indent, indent,
props: [], props: [],
@ -86,12 +88,14 @@ export function migrate(source) {
createBubbler: analysis.root.unique('createBubbler').name, createBubbler: analysis.root.unique('createBubbler').name,
bubble: analysis.root.unique('bubble').name, bubble: analysis.root.unique('bubble').name,
passive: analysis.root.unique('passive').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(), legacy_imports: new Set(),
script_insertions: new Set(), script_insertions: new Set(),
derived_components: new Map(), derived_components: new Map(),
derived_labeled_statements: new Set() derived_labeled_statements: new Set(),
has_svelte_self: false
}; };
if (parsed.module) { if (parsed.module) {
@ -118,12 +122,21 @@ export function migrate(source) {
state.script_insertions.size > 0 || state.script_insertions.size > 0 ||
state.props.length > 0 || state.props.length > 0 ||
analysis.uses_rest_props || analysis.uses_rest_props ||
analysis.uses_props; analysis.uses_props ||
state.has_svelte_self;
if (!parsed.instance && need_script) { if (!parsed.instance && need_script) {
str.appendRight(0, '<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 specifiers = [...state.legacy_imports].map((imported) => {
const local = state.names[imported]; const local = state.names[imported];
return imported === local ? imported : `${imported} as ${local}`; return imported === local ? imported : `${imported} as ${local}`;
@ -294,6 +307,7 @@ export function migrate(source) {
* scope: Scope; * scope: Scope;
* str: MagicString; * str: MagicString;
* analysis: ComponentAnalysis; * analysis: ComponentAnalysis;
* filename?: string;
* indent: 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: 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; * props_insertion_point: number;
@ -302,8 +316,9 @@ export function migrate(source) {
* names: Record<string, string>; * names: Record<string, string>;
* legacy_imports: Set<string>; * legacy_imports: Set<string>;
* script_insertions: Set<string>; * script_insertions: Set<string>;
* derived_components: Map<string, string>, * derived_components: Map<string, string>;
* derived_labeled_statements: Set<LabeledStatement> * derived_labeled_statements: Set<LabeledStatement>;
* has_svelte_self: boolean;
* }} State * }} State
*/ */
@ -724,6 +739,32 @@ const template = {
} }
next(); next();
}, },
SvelteSelf(node, { state, next }) {
if (!state.filename) {
const indent = guess_indent(state.str.original.substring(node.start, node.end));
state.str.prependRight(
node.start,
`<!-- @migration-task: migrate this by hand or call the migrate function with the filename of this file -->\n${indent}`
);
return;
}
let end = node.end - 1;
if (node.fragment.nodes.length > 0) {
end = node.fragment.nodes[0].start;
}
if (node.attributes.length > 0) {
end = node.attributes[0].start;
}
state.str.overwrite(node.start + 1, end - 1, `${state.names.svelte_self}`);
if (node.fragment.nodes.length > 0) {
const start_closing =
state.str.original.indexOf('<', node.fragment.nodes[node.fragment.nodes.length - 1].end) +
2;
state.str.overwrite(start_closing, node.end - 1, `${state.names.svelte_self}`);
}
state.has_svelte_self = true;
next();
},
SvelteElement(node, { state, next }) { SvelteElement(node, { state, next }) {
if (node.tag.type === 'Literal') { if (node.tag.type === 'Literal') {
let is_static = true; let is_static = true;

@ -0,0 +1,18 @@
<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>
{/if}

@ -0,0 +1,21 @@
<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>
{/if}

@ -0,0 +1,14 @@
{#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>
{/if}

@ -0,0 +1,20 @@
<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>
{/if}

@ -12,7 +12,7 @@ const { test, run } = suite<ParserTest>(async (config, cwd) => {
.replace(/\s+$/, '') .replace(/\s+$/, '')
.replace(/\r/g, ''); .replace(/\r/g, '');
const actual = migrate(input).code; const actual = migrate(input, `${cwd}/output.svelte`).code;
// run `UPDATE_SNAPSHOTS=true pnpm test migrate` to update parser tests // run `UPDATE_SNAPSHOTS=true pnpm test migrate` to update parser tests
if (process.env.UPDATE_SNAPSHOTS || !fs.existsSync(`${cwd}/output.svelte`)) { if (process.env.UPDATE_SNAPSHOTS || !fs.existsSync(`${cwd}/output.svelte`)) {

@ -80,7 +80,8 @@ export default class Compiler {
this.worker.postMessage({ this.worker.postMessage({
id, id,
type: 'migrate', 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 */ /** @param {import("../workers").MigrateMessageData} param0 */
function migrate({ id, source }) { function migrate({ id, source, filename }) {
try { try {
const result = svelte.migrate(source); const result = svelte.migrate(source, filename);
return { return {
id, id,

Loading…
Cancel
Save