unity-find-fault
Version:
A tool to find fault in unity project.
208 lines • 8.97 kB
JavaScript
import fs from "fs-extra";
import fg from "fast-glob";
import path from "path";
import { Project, SyntaxKind } from "ts-morph";
import _ from "lodash";
import { toolchain } from "../toolchain.js";
export class CodePatcher {
crtLogs = [];
async organizeImports() {
if (toolchain.opts.output && fs.existsSync(toolchain.opts.output)) {
await fs.unlink(toolchain.opts.output);
}
const tsRoot = path.join(toolchain.opts.projectRoot, 'TsScripts');
const project = new Project({
tsConfigFilePath: path.join(tsRoot, 'tsconfig.json')
});
// 'System/data/PetData.ts'
// const tss = await fg('System/activity/challenge/JJC/JingJiChangView.ts', { cwd: tsRoot, ignore: ['**/*.d.ts'] });
const tss = await fg('**/*.ts', { cwd: tsRoot, ignore: ['**/*.d.ts', 'root.ts'] });
for (const ts of tss) {
const tsf = path.join(tsRoot, ts);
this.crtLogs.length = 0;
const src = project.getSourceFileOrThrow(tsf);
src.organizeImports();
}
await project.save();
}
async patchAny() {
if (toolchain.opts.output && fs.existsSync(toolchain.opts.output)) {
await fs.unlink(toolchain.opts.output);
}
const tsRoot = path.join(toolchain.opts.projectRoot, 'TsScripts');
const project = new Project({
tsConfigFilePath: path.join(tsRoot, 'tsconfig.json')
});
// 'System/data/PetData.ts'
// const tss = await fg('System/activity/fuLi/DiZheZhiLuBuyView.ts', { cwd: tsRoot, ignore: ['**/*.d.ts'] });
const tss = await fg('**/*.ts', { cwd: tsRoot, ignore: ['**/*.d.ts'] });
for (const ts of tss) {
const tsf = path.join(tsRoot, ts);
this.crtLogs.length = 0;
const src = project.getSourceFileOrThrow(tsf);
let modifyCnt = 0;
const clss = src.getClasses();
clss.forEach((v) => {
modifyCnt += this.patchAnyInNode(v);
});
if (this.crtLogs.length > 0 && toolchain.opts.output) {
await fs.appendFile(toolchain.opts.output, tsf + '\n', 'utf-8');
await fs.appendFile(toolchain.opts.output, this.crtLogs.join('\n') + '\n', 'utf-8');
}
if (modifyCnt > 0) {
// console.log(modifyCnt + ' modified :', ts);
await src.save();
}
}
}
patchAnyInNode(node) {
let modifyCnt = 0;
if (node.isKind(SyntaxKind.VariableDeclaration) || node.isKind(SyntaxKind.PropertyDeclaration)) {
if (!node.getNameNode().isKind(SyntaxKind.ArrayBindingPattern)) {
const tt = node.getType().getText();
// console.log(node.getName(), tt);
if (tt == 'any[]' || tt == 'any') {
// console.log('any[] detected!', node.getName(), tt);
const refers = node.findReferencesAsNodes();
let assignTypes = [], callArgTypes = [], pushTypes = [];
for (const v of refers) {
if (v.isKind(SyntaxKind.Identifier)) {
// 通过赋值推断
const at = this.inferFromAssignment(v);
if (at) {
if (at != 'null[]' && at != 'undefined[]') {
assignTypes.push(at);
}
}
else {
// 通过CallExpression的参数推断
const cat = this.inferFromCallArg(v);
if (cat) {
callArgTypes.push(cat);
}
else if (tt == 'any[]') {
// 通过node.push推断
const pt = this.inferFromPush(v);
if (pt) {
pushTypes.push(...pt);
}
}
}
}
}
let typeStr = _.uniq(assignTypes).join(' | ') || _.uniq(callArgTypes).join(' | ');
if (!typeStr) {
pushTypes = _.uniq(pushTypes);
if (pushTypes.length > 1) {
typeStr = '(' + pushTypes.join(' | ') + ')[]';
}
else if (pushTypes.length == 1) {
typeStr = pushTypes[0] + '[]';
}
}
if (typeStr) {
this.crtLogs.push(`${node.getName()}: ${typeStr}`);
node.setType(typeStr);
modifyCnt++;
}
else {
this.crtLogs.push(`${node.getName()}: ???`);
}
}
}
}
else if (node.isKind(SyntaxKind.MethodDeclaration)) {
if (node.getReturnType().getText() == 'any') {
this.crtLogs.push(`${node.getName()}: ???`);
}
}
else {
const children = node.getChildren();
for (const child of children) {
modifyCnt += this.patchAnyInNode(child);
}
}
return modifyCnt;
}
inferFromAssignment(node) {
const be = node.getParentIfKind(SyntaxKind.BinaryExpression);
if (!be || !be.getOperatorToken().isKind(SyntaxKind.EqualsToken))
return null;
const left = be.getLeft();
const right = be.getRight();
if (right == node) {
return this.eusureNotAnyType(left.getType());
}
if (left == node) {
return this.eusureNotAnyType(right.getType());
}
return null;
}
inferFromCallArg(v) {
const ce = v.getParentIfKind(SyntaxKind.CallExpression);
if (!ce)
return null;
const exp = ce.getExpressionIfKind(SyntaxKind.PropertyAccessExpression);
if (exp) {
const exprNode = exp.getExpression();
if (exp.getName() == 'push' && exprNode.getType().isArray()) {
// 类似xxx.push(v),通过xxx的类型推断v的类型
// eg. System/data/PetData.ts group
return this.eusureNotAnyType(exprNode.getType().getArrayElementTypeOrThrow());
}
else {
const expNameNode = exp.getNameNode();
// 假如v是该方法调用的参数,通过方法签名判断
// eg. System/unit/hero/HeroController.ts serverPathLists
const args = ce.getArguments();
for (let i = 0, len = args.length; i < len; i++) {
if (args[i] == v) {
const defs = expNameNode.getDefinitionNodes();
for (const def of defs) {
if (!def.isKind(SyntaxKind.MethodDeclaration))
continue;
const params = def.getParameters();
const p = params[i];
if (p) {
return this.eusureNotAnyType(p.getType());
}
}
break;
}
}
}
}
return null;
}
inferFromPush(node) {
// 通过node.push推断
const pae = node.getParentIfKind(SyntaxKind.PropertyAccessExpression);
if (pae && pae.getName() == 'push') {
const ce = pae.getParentIfKind(SyntaxKind.CallExpression);
if (ce) {
const pushTypes = [];
const args = ce.getArguments();
for (const arg of args) {
const argType = this.eusureNotAnyType(arg.getType());
if (argType && argType != 'null' && argType != 'undefined') {
pushTypes.push(argType);
}
}
return pushTypes;
}
}
return null;
}
eusureNotAnyType(type) {
// let t = type.getText(undefined, TypeFormatFlags.UseFullyQualifiedType);
// if (!t.startsWith('GameConfig.') && !t.startsWith('Protocol.')) {
// t = type.getText(undefined, TypeFormatFlags.WriteClassExpressionAsTypeLiteral);
// }
const t = type.getText().replace(/^import\(.+?\)\./, '');
if (t.search(/\bany\b/) < 0) {
return t;
}
return null;
}
}
//# sourceMappingURL=CodePatcher.js.map