UNPKG

unity-find-fault

Version:

A tool to find fault in unity project.

208 lines 8.97 kB
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