oak-assistant/src/utils/ts-utils.ts

881 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import ts from 'typescript';
import { DocumentValue, RenderProps } from '../types';
import * as vscode from 'vscode';
import path, { join } from 'path';
import { pathConfig } from './paths';
/**
* 获取函数的返回值的attrs
* @param element 函数体节点
* @returns 返回值的attrs
*/
export const getAttrsFromFormData = (
formItem: ts.MethodDeclaration | ts.PropertyAssignment
): DocumentValue[] => {
let element: ts.Node = formItem;
const attrList: DocumentValue[] = [];
// 如果是isPropertyAssignment
if (ts.isPropertyAssignment(element)) {
element = element.initializer;
}
const getFromBlock = (block: ts.Block): DocumentValue[] => {
const attrs: DocumentValue[] = [];
// 拿到block的returnStatement
let returnStatement: ts.ReturnStatement | null = null;
ts.forEachChild(block, (grandChild) => {
if (ts.isReturnStatement(grandChild)) {
// 处理 return 语句
returnStatement = grandChild;
}
});
if (!returnStatement) {
return [];
}
ts.forEachChild(returnStatement, (returnChild) => {
if (ts.isObjectLiteralExpression(returnChild)) {
ts.forEachChild(returnChild, (objectChild) => {
if (ts.isShorthandPropertyAssignment(objectChild)) {
attrs.push({
value: objectChild.name.getText(),
pos: {
start: objectChild.name.getStart(),
end: objectChild.name.getEnd(),
},
});
}
if (ts.isPropertyAssignment(objectChild)) {
attrs.push({
value: objectChild.name.getText(),
pos: {
start: objectChild.name.getStart(),
end: objectChild.name.getEnd(),
},
});
}
if (ts.isSpreadAssignment(objectChild)) {
// 这里是展开运算符
if (ts.isSpreadAssignment(objectChild)) {
// 处理展开运算符
if (
ts.isObjectLiteralExpression(
objectChild.expression
)
) {
// 如果展开的是一个对象字面量表达式
objectChild.expression.properties.forEach(
(prop) => {
if (
ts.isPropertyAssignment(prop) ||
ts.isShorthandPropertyAssignment(
prop
)
) {
attrs.push({
value: prop.name.getText(),
pos: {
start: prop.name.getStart(),
end: prop.name.getEnd(),
},
});
}
}
);
} else if (
ts.isIdentifier(objectChild.expression)
) {
// 如果展开的是一个标识符,我们可能需要查找它的定义
console.error(
'Spread assignment with identifier:',
objectChild.expression.text
);
} else {
// 处理其他可能的情况
console.error(
'Spread assignment with expression type:',
objectChild.expression.kind
);
}
}
}
});
}
// 特殊情况,直接返回某一个变量
if (
ts.isIdentifier(returnChild) ||
ts.isBinaryExpression(returnChild)
) {
const addData = () => {
attrs.push({
value: '_$$data',
pos: {
start: returnChild.getStart(),
end: returnChild.getEnd(),
},
});
};
if (ts.isIdentifier(returnChild)) {
if (returnChild.text === 'data') {
addData();
}
} else {
if (ts.isIdentifier(returnChild.left)) {
if (returnChild.left.text === 'data') {
addData();
} else {
if (ts.isIdentifier(returnChild.right)) {
if (returnChild.right.text === 'data') {
addData();
}
}
}
}
}
}
// 联合对象
if (ts.isBinaryExpression(returnChild)) {
}
});
// 这里额外处理block中存在ifif或者switch的情况
ts.forEachChild(block, (node) => {
if (ts.isIfStatement(node)) {
const blocks: ts.Block[] = [];
node.forEachChild((ifChild) => {
if (ts.isBlock(ifChild)) {
blocks.push(ifChild);
}
});
blocks.forEach((b) => {
attrs.push(...getFromBlock(b));
});
} else {
if (ts.isSwitchStatement(node)) {
const blocks: ts.Block[] = [];
node.forEachChild((switchChild) => {
if (ts.isCaseBlock(switchChild)) {
const caseClause: (
| ts.CaseClause
| ts.DefaultClause
)[] = [];
switchChild.forEachChild((caseChild) => {
if (ts.isCaseClause(caseChild)) {
caseClause.push(caseChild);
} else {
if (ts.isDefaultClause(caseChild)) {
caseClause.push(caseChild);
}
}
});
caseClause.forEach((c) => {
c.forEachChild((caseChild) => {
if (ts.isBlock(caseChild)) {
blocks.push(caseChild);
}
});
});
}
});
blocks.forEach((b) => {
attrs.push(...getFromBlock(b));
});
}
}
});
return attrs;
};
element.getChildren().forEach((child) => {
if (ts.isBlock(child)) {
attrList.push(...getFromBlock(child));
}
});
return attrList;
};
/**
* 获取函数的返回值的attrs
* @param element 函数体节点
* @returns 返回值的attrs
*/
export const getAttrsFromMethods = (
element: ts.ObjectLiteralElementLike
): DocumentValue[] => {
const attrs: DocumentValue[] = [];
ts.forEachChild(element, (child) => {
if (ts.isObjectLiteralExpression(child)) {
ts.forEachChild(child, (objectChild) => {
if (
ts.isMethodDeclaration(objectChild) ||
ts.isShorthandPropertyAssignment(objectChild) ||
ts.isPropertyAssignment(objectChild)
) {
attrs.push({
value: objectChild.name.getText(),
pos: {
start: objectChild.name.getStart(),
end: objectChild.name.getEnd(),
},
});
}
});
}
});
return attrs;
};
export const getAttrsFromDatas = (
element: ts.ObjectLiteralElementLike
): DocumentValue[] => {
const attrs: DocumentValue[] = [];
ts.forEachChild(element, (child) => {
if (ts.isObjectLiteralExpression(child)) {
ts.forEachChild(child, (objectChild) => {
if (
ts.isMethodDeclaration(objectChild) ||
ts.isShorthandPropertyAssignment(objectChild) ||
ts.isPropertyAssignment(objectChild)
) {
attrs.push({
value: objectChild.name.getText(),
pos: {
start: objectChild.name.getStart(),
end: objectChild.name.getEnd(),
},
});
}
});
}
});
return attrs;
};
/**
* 获取函数的返回值的attrs
* @param element 函数体节点
* @returns 返回值的attrs
*/
export const getAttrsFromProperties = (
element: ts.ObjectLiteralElementLike
): DocumentValue[] => {
const attrs: DocumentValue[] = [];
ts.forEachChild(element, (child) => {
if (ts.isObjectLiteralExpression(child)) {
ts.forEachChild(child, (objectChild) => {
if (
ts.isPropertyAssignment(objectChild) ||
ts.isShorthandPropertyAssignment(objectChild)
) {
attrs.push({
value: objectChild.name.getText(),
pos: {
start: objectChild.name.getStart(),
end: objectChild.name.getEnd(),
},
});
}
});
}
});
return attrs;
};
/**
* 获取函数的返回值的attrs
* @param sourceFile 源文件
* @returns 返回值的attrs
*
* 这里实际上有四种情况:
*
* export default function render
* function render....export default render;
* const render....export default render;
* export default [ArrowFunction]
*
* 所以最开始先考虑四种情况拿到functionNode然后再去拿到第一个参数
*/
export const getWebComponentPropsData = (
sourceFile: ts.SourceFile
): RenderProps | undefined => {
let functionNode: ts.FunctionDeclaration | ts.ArrowFunction | undefined;
// 遍历 AST 找到目标函数节点
ts.forEachChild(sourceFile, (node) => {
if (
ts.isFunctionDeclaration(node) &&
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
) {
// Case 1: export default function render(...)
functionNode = node;
} else if (ts.isFunctionDeclaration(node)) {
// Case 2: function render(...) export default render;
functionNode = node;
} else if (ts.isVariableStatement(node)) {
// Case 3: const render = (...) => {...} export default render;
const declaration = node.declarationList.declarations[0];
if (
ts.isVariableDeclaration(declaration) &&
ts.isArrowFunction(declaration.initializer!)
) {
functionNode = declaration.initializer;
}
} else if (
ts.isExportAssignment(node) &&
ts.isArrowFunction(node.expression)
) {
// Case 4: export default (...) => {...}
functionNode = node.expression;
}
});
if (!functionNode || !functionNode.parameters.length) {
return undefined;
}
const firstParam = functionNode.parameters[0];
if (firstParam.type && ts.isTypeReferenceNode(firstParam.type)) {
// 如果名称不为WebComponentProps
if (firstParam.type.typeName.getText() !== 'WebComponentProps') {
return undefined;
}
const args = firstParam.type.typeArguments;
// dictName: TypeReference -> Identifier
// entityName: LiteralType -> StringLiteral
// isList: LiteralType -> TrueKeyword | FalseKeyword
// attrList: TypeLiteral -> PropertySignature
// methodList: TypeLiteral -> PropertySignature
if (!ts.isTypeReferenceNode(args![0])) {
console.log('');
return undefined;
}
const dictNameNode = args![0].typeName;
const dictName: DocumentValue = {
value: dictNameNode.getText(),
pos: {
start: dictNameNode.getStart(),
end: dictNameNode.getEnd(),
},
};
// 看看是什么节点
const arg1 = args![1];
console.log(arg1.kind);
let entityName: DocumentValue | undefined;
if (ts.isTypeOperatorNode(arg1)) {
// 那要确保是keyof EntityDict然后标记为空白的虚拟节点就好
const op = arg1.operator;
if (op !== ts.SyntaxKind.KeyOfKeyword) {
return undefined;
}
const entityNameNode = arg1.type;
entityName = {
value: entityNameNode.getText(),
pos: {
start: arg1.getStart(),
end: arg1.getEnd(),
},
};
} else {
if (!ts.isLiteralTypeNode(args![1])) {
return undefined;
}
const nameRawTextNode = args![1].literal;
const nameRawText = args![1].literal.getText();
entityName = {
value: nameRawText.substring(1, nameRawText.length - 1),
pos: {
start: nameRawTextNode.getStart(),
end: nameRawTextNode.getEnd(),
},
};
}
if (!ts.isLiteralTypeNode(args![2])) {
return undefined;
}
const isListNode = args![2].literal;
const isList: DocumentValue = {
value: isListNode.getText() === 'true' ? true : false,
pos: {
start: isListNode.getStart(),
end: isListNode.getEnd(),
},
};
const data: RenderProps = {
dictName,
entityName,
isList,
};
// 前面三个是必须的,后面两个是可选的
if (args![3] && ts.isTypeLiteralNode(args![3])) {
const attrList: DocumentValue[] = [];
args![3].members.forEach((member) => {
if (ts.isPropertySignature(member)) {
attrList.push({
value: member.name.getText(),
pos: {
start: member.name.getStart(),
end: member.name.getEnd(),
},
});
}
});
data.attrList = attrList;
}
if (args![4] && ts.isTypeLiteralNode(args![4])) {
const methodList: DocumentValue[] = [];
args![4].members.forEach((member) => {
if (ts.isPropertySignature(member)) {
methodList.push({
value: member.name.getText(),
pos: {
start: member.name.getStart(),
end: member.name.getEnd(),
},
});
}
});
data.methodList = methodList;
}
return data;
}
return undefined;
};
export async function addAttrToFormData(
documentUri: vscode.Uri,
attrName: string
) {
// const attrName = message.match(/属性(\w+)未在index.ts中定义/)?.[1];
if (!attrName) {
vscode.window.showErrorMessage('无法识别属性名');
return;
}
const indexPath = vscode.Uri.file(join(documentUri.fsPath, '../index.ts'));
const indexDocument = await vscode.workspace.openTextDocument(indexPath);
const indexText = indexDocument.getText();
const sourceFile = ts.createSourceFile(
'index.ts',
indexText,
ts.ScriptTarget.Latest,
true
);
let formDataPos: number | null = null;
let insertPos: number | null = null;
function visitNode(node: ts.Node) {
if (
ts.isCallExpression(node) &&
ts.isIdentifier(node.expression) &&
node.expression.text === 'OakComponent'
) {
const arg = node.arguments[0];
if (ts.isObjectLiteralExpression(arg)) {
const formData = arg.properties.find(
(prop): prop is ts.MethodDeclaration => {
return (
(ts.isPropertyAssignment(prop) ||
ts.isMethodDeclaration(prop)) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'formData'
);
}
) as ts.MethodDeclaration | ts.PropertyAssignment | undefined;
if (formData) {
let returnStatement;
if (ts.isMethodDeclaration(formData)) {
returnStatement = formData.body?.statements.find(
ts.isReturnStatement
);
} else if (ts.isPropertyAssignment(formData)) {
const functionDeclaration = formData.initializer;
const block = ts.forEachChild(
functionDeclaration,
(child) => {
if (ts.isBlock(child)) {
return child;
}
}
);
returnStatement = block?.statements.find(
ts.isReturnStatement
);
}
if (
returnStatement &&
ts.isObjectLiteralExpression(
returnStatement.expression!
)
) {
formDataPos = formData.pos;
insertPos = returnStatement.expression.properties.end;
}
}
}
}
ts.forEachChild(node, visitNode);
}
visitNode(sourceFile);
if (formDataPos !== null && insertPos !== null) {
const edit = new vscode.WorkspaceEdit();
// 获取插入位置的前一个字符
const prevChar = indexDocument.getText(
new vscode.Range(
indexDocument.positionAt(insertPos - 1),
indexDocument.positionAt(insertPos)
)
);
// 根据前一个字符是否为逗号来决定插入的文本
let insertText;
if (prevChar.trim() === ',') {
insertText = `\n ${attrName}: this.props.${attrName}`;
} else {
insertText = `,\n ${attrName}: this.props.${attrName}`;
}
edit.insert(indexPath, indexDocument.positionAt(insertPos), insertText);
await vscode.workspace.applyEdit(edit);
// vscode.window.showInformationMessage(`属性 ${attrName} 已添加到 formData`);
const args = {
filePath: indexPath.fsPath,
start: insertPos,
end: insertPos + insertText.length,
};
await vscode.commands.executeCommand(
'oak-assistant.jumpToPosition',
args
);
} else {
vscode.window.showErrorMessage('无法在 index.ts 中找到合适的插入位置');
}
}
export async function addMethodToMethods(
documentUri: vscode.Uri,
methodName: string
) {
if (!methodName) {
vscode.window.showErrorMessage('无法识别方法名');
return;
}
const indexPath = vscode.Uri.file(join(documentUri.fsPath, '../index.ts'));
const indexDocument = await vscode.workspace.openTextDocument(indexPath);
const indexText = indexDocument.getText();
const sourceFile = ts.createSourceFile(
'index.ts',
indexText,
ts.ScriptTarget.Latest,
true
);
let oakCreateObj: ts.ObjectLiteralExpression | null = null;
let methodsPos: number | null = null;
let insertPos: number | null = null;
function visitNode(node: ts.Node) {
if (
ts.isCallExpression(node) &&
ts.isIdentifier(node.expression) &&
node.expression.text === 'OakComponent'
) {
const arg = node.arguments[0];
if (ts.isObjectLiteralExpression(arg)) {
oakCreateObj = arg;
const methods = arg.properties.find(
(prop): prop is ts.PropertyAssignment =>
ts.isPropertyAssignment(prop) &&
ts.isIdentifier(prop.name) &&
prop.name.text === 'methods'
);
if (
methods &&
ts.isObjectLiteralExpression(methods.initializer)
) {
methodsPos = methods.pos;
insertPos = methods.initializer.properties.end;
}
}
}
ts.forEachChild(node, visitNode);
}
visitNode(sourceFile);
if (methodsPos === null || insertPos === null) {
if (!oakCreateObj) {
vscode.window.showErrorMessage(
'无法在 index.ts 中找到 OakComponent 调用'
);
}
// 在oakCreateObj找到最后一个属性的位置并中创建一个属性为methods的对象字面量
let lastPropPos = oakCreateObj!.properties.end;
let insertText = `\n methods: {\n ${methodName}() {},\n }`;
if (oakCreateObj!.properties.length) {
const lastProp =
oakCreateObj!.properties[oakCreateObj!.properties.length - 1];
lastPropPos = lastProp.end;
insertText = `,${insertText}`;
}
const edit = new vscode.WorkspaceEdit();
edit.insert(
indexPath,
indexDocument.positionAt(lastPropPos),
insertText
);
await vscode.workspace.applyEdit(edit);
//保存
await indexDocument.save();
// vscode.window.showTextDocument(indexDocument.uri);
const args = {
filePath: indexPath.fsPath,
start: lastPropPos,
end: lastPropPos + insertText.length,
};
await vscode.commands.executeCommand(
'oak-assistant.jumpToPosition',
args
);
return;
}
const edit = new vscode.WorkspaceEdit();
// 获取插入位置的前一个字符
const prevChar = indexDocument.getText(
new vscode.Range(
indexDocument.positionAt(insertPos - 1),
indexDocument.positionAt(insertPos)
)
);
// 根据前一个字符是否为逗号来决定插入的文本
let insertText;
if (prevChar.trim() === ',') {
insertText = `\n ${methodName}() {},`;
} else {
insertText = `,\n ${methodName}() {},`;
}
edit.insert(indexPath, indexDocument.positionAt(insertPos), insertText);
await vscode.workspace.applyEdit(edit);
// vscode.window.showInformationMessage(
// `方法 ${methodName} 已添加到 methods`
// );
await indexDocument.save();
// 跳转文件
// vscode.window.showTextDocument(indexDocument.uri);
const args = {
filePath: indexPath.fsPath,
start: insertPos,
end: insertPos + insertText.length,
};
await vscode.commands.executeCommand('oak-assistant.jumpToPosition', args);
}
/**
* 创建项目的程序
* @param filePath 要检查的文件路径
*/
export const createProjectProgram = (filePath: string) => {
const projectRoot = pathConfig.projectHome;
// 查找配置文件
const configFileName = ts.findConfigFile(
projectRoot,
ts.sys.fileExists,
'tsconfig.json'
);
if (!configFileName) {
throw new Error("Could not find a valid 'tsconfig.json'.");
}
// 读取配置文件
const configFile = ts.readConfigFile(configFileName, ts.sys.readFile);
// 解析配置文件内容
const { options, fileNames, projectReferences } =
ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(configFileName)
);
// 确保文件路径是绝对路径
const absoluteFilePath = path.isAbsolute(filePath)
? filePath
: path.join(projectRoot, filePath);
// 创建程序
const program = ts.createProgram({
rootNames: [absoluteFilePath],
options,
projectReferences,
});
return program;
};
export const resolveModulePath = (
importModule: string,
file?: string
): [sourceFile: ts.SourceFile, path: string] => {
const programPath = file || join(pathConfig.oakAppDomainHome, 'index.ts');
const program = createProjectProgram(programPath);
// 创建编译器主机
const compilerHost = ts.createCompilerHost(program.getCompilerOptions());
// 解析模块
const resolvedModule = ts.resolveModuleName(
importModule,
programPath,
program.getCompilerOptions(),
compilerHost
);
if (!resolvedModule.resolvedModule) {
throw new Error(`Could not resolve module: ${importModule}`);
}
// 获取源文件
const sourceFile = program.getSourceFile(
resolvedModule.resolvedModule.resolvedFileName
);
if (!sourceFile) {
throw new Error(
`Could not find source file for module: ${importModule}`
);
}
return [sourceFile, resolvedModule.resolvedModule.resolvedFileName];
};
// 判断类型是否是 Promise
export function isPromiseType(
type: ts.Type,
typeChecker: ts.TypeChecker
): boolean {
// 检查是否直接是 Promise 类型
if (type.symbol && type.symbol.name === 'Promise') {
return true;
}
// 检查是否是联合类型中的 Promise
if (type.isUnion()) {
return type.types.some((t) => isPromiseType(t, typeChecker));
}
// 检查基类
const baseTypes = type.getBaseTypes();
if (baseTypes) {
return baseTypes.some((t) => isPromiseType(t, typeChecker));
}
return false;
}
/**
* 将 ts.PropertyAssignment[] 转换为 JS 对象
*/
export function translateAttributes(attributes: ts.PropertyAssignment[]): Record<string, any> {
const result: Record<string, any> = {};
for (const prop of attributes) {
const key = getPropertyName(prop.name);
const value = evaluateExpression(prop.initializer);
result[key] = value;
}
return result;
}
/**
* 提取属性名(支持标识符和字符串字面量)
*/
function getPropertyName(name: ts.PropertyName): string {
if (ts.isIdentifier(name)) {
return name.text;
} else if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
return name.text;
} else {
throw new Error("Unsupported property name type");
}
}
/**
* 递归地将 TS 表达式转换为 JS 值
*/
function evaluateExpression(expr: ts.Expression): any {
if (ts.isObjectLiteralExpression(expr)) {
const obj: Record<string, any> = {};
for (const prop of expr.properties) {
if (ts.isPropertyAssignment(prop)) {
const key = getPropertyName(prop.name);
const value = evaluateExpression(prop.initializer);
obj[key] = value;
} else {
throw new Error("Only property assignments are supported in object literals");
}
}
return obj;
} else if (ts.isArrayLiteralExpression(expr)) {
return expr.elements.map(evaluateExpression);
} else if (ts.isStringLiteral(expr)) {
return expr.text;
} else if (ts.isNumericLiteral(expr)) {
return Number(expr.text);
} else if (expr.kind === ts.SyntaxKind.TrueKeyword) {
return true;
} else if (expr.kind === ts.SyntaxKind.FalseKeyword) {
return false;
} else if (expr.kind === ts.SyntaxKind.NullKeyword) {
return null;
} else if (ts.isIdentifier(expr)) {
return expr.text; // 返回标识符名(例如引用类型或枚举名)
} else if (ts.isPropertyAccessExpression(expr)) {
// const left = evaluateExpression(expr.expression);
// const right = expr.name.text;
// if (typeof left === "string") {
// return `${left}.${right}`;
// } else if (left && typeof left === "object" && right in left) {
// return left[right];
// } else {
// return `${left}.${right}`;
// }
return `unresolved`;
} else {
console.log("发现不支持的表达式类型:", ts.SyntaxKind[expr.kind], "在文件", expr.getSourceFile().fileName, "位置", expr.getStart(), "-", expr.getEnd());
throw new Error(`Unsupported expression kind: ${ts.SyntaxKind[expr.kind]}`);
}
}