174 lines
6.2 KiB
TypeScript
174 lines
6.2 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import fsp from 'node:fs/promises';
|
|
import {
|
|
type ArrowFunctionExpression,
|
|
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
|
|
type Function,
|
|
type MagicString,
|
|
type Statement,
|
|
parseSync,
|
|
} from 'oxc-parser';
|
|
import { walk } from 'oxc-walker';
|
|
|
|
function sourceTextFromNode(
|
|
context: { magicString?: MagicString },
|
|
node: { start: number; end: number }
|
|
): string {
|
|
const magicString = context.magicString;
|
|
assert(magicString, 'magicString should be defined');
|
|
const start = node.start;
|
|
const end = node.end;
|
|
return magicString.getSourceText(start, end);
|
|
}
|
|
|
|
export async function rewriteObservableSubscribeTofirstValueFrom(
|
|
filename: string,
|
|
content?: string
|
|
) {
|
|
const code = content ?? (await fsp.readFile(filename, 'utf-8'));
|
|
const parsedResult = parseSync('index.ts', code);
|
|
const magicString = parsedResult.magicString;
|
|
walk(parsedResult, {
|
|
leave(node, _, context) {
|
|
const transformExprs = <T extends Statement[]>(
|
|
children: T
|
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: <explanation>
|
|
): T => {
|
|
const newChildren: T = [] as any as T;
|
|
for (const child of children) {
|
|
if (
|
|
child.type === 'ExpressionStatement' &&
|
|
child.expression.type === 'CallExpression' &&
|
|
child.expression.callee.type === 'StaticMemberExpression' &&
|
|
child.expression.callee.property.name === 'subscribe'
|
|
) {
|
|
let next: ArrowFunctionExpression | Function | undefined;
|
|
let error: ArrowFunctionExpression | Function | undefined;
|
|
let complete: ArrowFunctionExpression | Function | undefined;
|
|
|
|
if (child.expression.arguments[0]?.type === 'ObjectExpression') {
|
|
const obj = child.expression.arguments[0];
|
|
for (const prop of obj.properties) {
|
|
if (
|
|
prop.type === 'ObjectProperty' &&
|
|
prop.key.type === 'Identifier' &&
|
|
(prop.value.type === 'FunctionExpression' ||
|
|
prop.value.type === 'ArrowFunctionExpression')
|
|
) {
|
|
if (prop.key.name === 'next') {
|
|
next = prop.value;
|
|
} else if (prop.key.name === 'error') {
|
|
error = prop.value;
|
|
} else if (prop.key.name === 'complete') {
|
|
complete = prop.value;
|
|
}
|
|
}
|
|
}
|
|
} else if (
|
|
child.expression.arguments.find(
|
|
(arg) =>
|
|
arg.type === 'FunctionExpression' ||
|
|
arg.type === 'ArrowFunctionExpression'
|
|
)
|
|
) {
|
|
const args: Array<
|
|
Function | ArrowFunctionExpression | undefined
|
|
> = child.expression.arguments.map((arg) =>
|
|
arg.type === 'FunctionExpression' ||
|
|
arg.type === 'ArrowFunctionExpression'
|
|
? arg
|
|
: undefined
|
|
);
|
|
next = args[0];
|
|
error = args[1];
|
|
complete = args[2];
|
|
}
|
|
let newContent = `await firstValueFrom(${sourceTextFromNode(context, child.expression.callee.object)});`;
|
|
|
|
if (next) {
|
|
const nextParam =
|
|
next?.params?.items?.[0]?.type === 'FormalParameter'
|
|
? sourceTextFromNode(context, next.params.items[0])
|
|
: undefined;
|
|
|
|
if (nextParam) {
|
|
newContent = `const ${nextParam} = ${newContent}`;
|
|
}
|
|
newContent += (next.body?.statements || [])
|
|
.map((s) => sourceTextFromNode(context, s))
|
|
.join('\n');
|
|
}
|
|
|
|
if (error || complete) {
|
|
const errorParam =
|
|
error?.params?.items?.[0]?.type === 'FormalParameter' &&
|
|
error.params.items[0].pattern.type === 'Identifier'
|
|
? sourceTextFromNode(context, error.params.items[0])
|
|
: 'err';
|
|
const errorParamName =
|
|
error?.params?.items?.[0]?.type === 'FormalParameter' &&
|
|
error.params.items[0].pattern.type === 'Identifier'
|
|
? error.params.items[0].pattern.name
|
|
: 'err';
|
|
|
|
let errorBody = '';
|
|
if (error) {
|
|
errorBody += (error.body?.statements || [])
|
|
.map((s) => sourceTextFromNode(context, s))
|
|
.join('\n');
|
|
}
|
|
if (complete) {
|
|
const completBody = `if (${errorParamName} instanceof EmptyError) { ${(complete.body?.statements || []).map((s) => sourceTextFromNode(context, s)).join('\n')}}`;
|
|
if (errorBody) {
|
|
errorBody = `${completBody} else { ${errorBody} }`;
|
|
} else {
|
|
errorBody = completBody;
|
|
}
|
|
}
|
|
|
|
newContent = `try { ${newContent} } catch (${errorParam}) { ${errorBody} }`;
|
|
}
|
|
|
|
const newNodes = parseSync('index.html', newContent).program.body;
|
|
|
|
magicString.remove(child.start, child.end);
|
|
magicString.appendLeft(child.start, newContent);
|
|
|
|
newChildren.push(...newNodes);
|
|
} else {
|
|
newChildren.push(child as any);
|
|
}
|
|
}
|
|
return newChildren;
|
|
};
|
|
if ('body' in node && Array.isArray(node.body) && node.body.length > 0) {
|
|
const children = node.body;
|
|
node.body = transformExprs(children as any)!;
|
|
} else if (
|
|
'body' in node &&
|
|
node.body &&
|
|
'type' in node.body &&
|
|
node.body.type === 'FunctionBody'
|
|
) {
|
|
const children = node.body.statements;
|
|
node.body.statements = transformExprs(children)!;
|
|
}
|
|
},
|
|
});
|
|
|
|
const result = magicString.toString();
|
|
|
|
return result;
|
|
}
|
|
|
|
export async function rewriteAllObservableSubscribeTofirstValueFrom(
|
|
pattern: string | string[]
|
|
) {
|
|
const files = fsp.glob(pattern);
|
|
for await (const file of files) {
|
|
const result = await rewriteObservableSubscribeTofirstValueFrom(file);
|
|
|
|
await fsp.writeFile(file, result, 'utf-8');
|
|
}
|
|
}
|