svelte-migrate
Version:
A CLI for migrating Svelte(Kit) codebases
123 lines (100 loc) • 3.57 kB
JavaScript
import ts from 'typescript';
import {
automigration,
error,
get_function_node,
get_object_nodes,
manual_migration,
manual_return_migration,
parse,
rewrite_returns,
rewrite_type,
unwrap,
uppercase_migration
} from '../utils.js';
import * as TASKS from '../tasks.js';
import { dedent } from '../../../utils.js';
const give_up = `${error('Update +page.server.js', TASKS.PAGE_ENDPOINT)}\n\n`;
/**
* @param {string} content
* @param {string} filename
* @returns {string}
*/
export function migrate_page_server(content, filename) {
const file = parse(content);
if (!file) return give_up + content;
const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].filter((name) =>
file.exports.map.has(name)
);
// If user didn't do the uppercase verbs migration yet, do it here on the fly.
const uppercased = uppercase_migration(methods, file);
if (!uppercased) {
return give_up + content;
} else if (uppercased !== content) {
return migrate_page_server(uppercased, filename);
}
const non_get_methods = methods.filter((name) => name !== 'GET');
const unmigrated = new Set(methods);
const match = /\+(?:(page)|(layout(?:-([^.@]+))?))/.exec(filename);
const load_name = match?.[3]
? `LayoutServerLoad.${match[3]}`
: match?.[2]
? 'LayoutServerLoad'
: 'PageServerLoad';
const has_load = file.exports.map.has('GET');
const has_action = non_get_methods.some((method) => file.exports.map.has(method));
for (const statement of file.ast.statements) {
const GET_id = file.exports.map.get('GET');
if (GET_id) {
const GET = get_function_node(statement, GET_id);
if (GET) {
if (GET.body) {
// possible TODOs — handle errors and redirects
rewrite_returns(GET.body, (expr, node) => {
const value = unwrap(expr);
const nodes = ts.isObjectLiteralExpression(value) && get_object_nodes(value);
if (!nodes || nodes.headers || (nodes.status && nodes.status.getText() !== '200')) {
manual_return_migration(node || GET, file.code, TASKS.PAGE_ENDPOINT);
return;
}
automigration(value, file.code, dedent(nodes.body?.getText() || ''));
});
}
rewrite_type(GET, file.code, 'RequestHandler', load_name);
if (ts.isFunctionDeclaration(GET) && GET.name) {
automigration(GET.name, file.code, 'load');
} else if (ts.isVariableDeclaration(GET.parent) && ts.isIdentifier(GET.parent.name)) {
automigration(GET.parent.name, file.code, 'load');
} else {
manual_migration(GET, file.code, 'Rename GET to load', TASKS.PAGE_ENDPOINT);
}
unmigrated.delete('GET');
}
}
for (const method of non_get_methods) {
const fn = get_function_node(statement, /** @type{string} */ (file.exports.map.get(method)));
if (fn?.body) {
rewrite_returns(fn.body, (expr, node) => {
manual_return_migration(node || fn, file.code, TASKS.PAGE_ENDPOINT);
});
rewrite_type(fn, file.code, 'RequestHandler', 'Action');
unmigrated.delete(method);
}
}
if (ts.isImportDeclaration(statement) && statement.importClause) {
const bindings = statement.importClause.namedBindings;
if (bindings && !ts.isNamespaceImport(bindings)) {
for (const binding of bindings.elements) {
if (binding.name.escapedText === 'RequestHandler') {
const types = [has_load && load_name, has_action && 'Action'].filter(Boolean);
file.code.overwrite(binding.getStart(), binding.getEnd(), types.join(', '));
}
}
}
}
}
if (unmigrated.size) {
return give_up + file.code.toString();
}
return file.code.toString();
}