feat: 增强Service层转换器

 DTO导入增强:
- 清理泛型语法: List<String> → List
- 跳过Java基础类型(List/Map/String等)
- 防止异常import语句

 VO类型自动识别:
- 从方法体中提取VO类型
- 正则匹配所有XxxVo/Dto/Param引用
- 自动添加到导入列表

 Java API转换器(新增):
- path.toFile() → path
- file.path → file
- Files.* → fs.*
- Paths.get() → path.join()
- Charset/Encoding处理

📊 效果: 14086 errors (持平)
🔧 原因: 这些是结构性/业务逻辑错误,需要手动介入
This commit is contained in:
wanwu
2025-10-29 21:16:18 +08:00
parent b20db79771
commit dbe61ed21f
3 changed files with 168 additions and 21 deletions

View File

@@ -21,6 +21,7 @@ const StringConverter = require('./utils/string.converter');
const CollectionConverter = require('./utils/collection.converter');
const JsonConverter = require('./utils/json.converter');
const ObjectConverter = require('./utils/object.converter');
const JavaApiConverter = require('./utils/java-api.converter');
const QueryWrapperConverter = require('./mybatis/query-wrapper.converter');
const MapperConverter = require('./mybatis/mapper.converter');
@@ -45,6 +46,7 @@ class ServiceMethodConverter {
this.collection = new CollectionConverter();
this.json = new JsonConverter();
this.object = new ObjectConverter();
this.javaApi = new JavaApiConverter();
this.queryWrapper = new QueryWrapperConverter();
this.mapper = new MapperConverter();
@@ -90,6 +92,7 @@ class ServiceMethodConverter {
tsBody = this.collection.convert(tsBody);
tsBody = this.json.convert(tsBody);
tsBody = this.object.convert(tsBody);
tsBody = this.javaApi.convert(tsBody);
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【阶段4】MyBatis → TypeORM转换
@@ -173,6 +176,15 @@ class ServiceMethodConverter {
const configImports = this.config.analyzeImports(convertedBody);
configImports.forEach(imp => imports.boot.add(imp));
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Java API转换】fs/path (Node.js模块)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const javaApiImports = this.javaApi.analyzeImports(convertedBody);
javaApiImports.forEach(imp => {
if (imp === 'node:fs') imports.nodeModules.add('fs');
if (imp === 'node:path') imports.nodeModules.add('path');
});
return {
nestjs: Array.from(imports.nestjs),
boot: Array.from(imports.boot),

View File

@@ -0,0 +1,114 @@
/**
* Java API转换器
*
* Java特有API → Node.js/TypeScript API
*
* - path.toFile() → path (Path对象在Node.js中就是string)
* - file.path → file (File对象在Node.js中就是path string)
* - Files.readString(path) → fs.readFileSync(path, 'utf-8')
* - Files.write(path, content) → fs.writeFileSync(path, content)
*/
class JavaApiConverter {
/**
* 转换Java API
*/
convert(javaCode) {
let tsCode = javaCode;
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【File/Path API】→ Node.js string paths
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// path.toFile() → path (在Node.js中path就是string)
tsCode = tsCode.replace(/(\w+)\.toFile\(\)/g, '$1');
// file.path → file (在Node.js中file就是path string)
tsCode = tsCode.replace(/(\w+)\.path\b/g, '$1');
// Paths.get(...) → path.join(...)
tsCode = tsCode.replace(/Paths\.get\(/g, 'path.join(');
// File.separator → path.sep
tsCode = tsCode.replace(/File\.separator/g, 'path.sep');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Files API】→ fs module
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Files.readString(path) → fs.readFileSync(path, 'utf-8')
tsCode = tsCode.replace(/Files\.readString\(([^)]+)\)/g, "fs.readFileSync($1, 'utf-8')");
// Files.readAllBytes(path) → fs.readFileSync(path)
tsCode = tsCode.replace(/Files\.readAllBytes\(([^)]+)\)/g, 'fs.readFileSync($1)');
// Files.write(path, content) → fs.writeFileSync(path, content)
tsCode = tsCode.replace(/Files\.write\(([^,]+),\s*([^)]+)\)/g, 'fs.writeFileSync($1, $2)');
// Files.exists(path) → fs.existsSync(path)
tsCode = tsCode.replace(/Files\.exists\(([^)]+)\)/g, 'fs.existsSync($1)');
// Files.createDirectories(path) → fs.mkdirSync(path, { recursive: true })
tsCode = tsCode.replace(/Files\.createDirectories\(([^)]+)\)/g, 'fs.mkdirSync($1, { recursive: true })');
// Files.delete(path) → fs.unlinkSync(path)
tsCode = tsCode.replace(/Files\.delete\(([^)]+)\)/g, 'fs.unlinkSync($1)');
// Files.list(path) → fs.readdirSync(path)
tsCode = tsCode.replace(/Files\.list\(([^)]+)\)/g, 'fs.readdirSync($1)');
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Stream/Optional API】→ TypeScript
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// .orElse(null) → || null
tsCode = tsCode.replace(/\.orElse\(null\)/g, ' || null');
// .orElse(defaultValue) → || defaultValue
tsCode = tsCode.replace(/\.orElse\(([^)]+)\)/g, ' || $1');
// .isPresent() → (删除TypeScript直接判断truthy)
// 注意:这个需要上下文判断,暂时保留
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 【Charset/Encoding】→ 字符串编码
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Charset.forName("UTF-8") → 'utf-8'
tsCode = tsCode.replace(/Charset\.forName\("UTF-8"\)/g, "'utf-8'");
tsCode = tsCode.replace(/Charset\.forName\('UTF-8'\)/g, "'utf-8'");
// System.getProperty("sun.jnu.encoding") → 'utf-8' (默认UTF-8)
tsCode = tsCode.replace(/System\.getProperty\("sun\.jnu\.encoding"\)/g, "'utf-8'");
// StandardCharsets.UTF_8 → 'utf-8'
tsCode = tsCode.replace(/StandardCharsets\.UTF_8/g, "'utf-8'");
return tsCode;
}
/**
* 分析需要的imports
*/
analyzeImports(tsCode) {
const imports = new Set();
// 检查是否使用了fs
if (tsCode.includes('fs.readFileSync') || tsCode.includes('fs.writeFileSync') ||
tsCode.includes('fs.existsSync') || tsCode.includes('fs.mkdirSync') ||
tsCode.includes('fs.unlinkSync') || tsCode.includes('fs.readdirSync') ||
tsCode.includes('fs.statSync')) {
imports.add('node:fs');
}
// 检查是否使用了path
if (tsCode.includes('path.join') || tsCode.includes('path.sep') ||
tsCode.includes('path.dirname') || tsCode.includes('path.basename')) {
imports.add('node:path');
}
return Array.from(imports);
}
}
module.exports = JavaApiConverter;

View File

@@ -177,7 +177,7 @@ ${methods}
}
/**
* 从方法参数中提取DTO/VO类型
* 从方法参数和方法体中提取DTO/VO类型
*
* @param {object} javaService - Java服务对象
* @returns {string[]} DTO/VO类型列表
@@ -190,27 +190,40 @@ ${methods}
}
javaService.methods.forEach(method => {
if (!method.parameters) {
return;
// 1. 从参数中提取
if (method.parameters) {
method.parameters.forEach(param => {
const paramType = param.type;
if (!paramType) {
return;
}
// 检查是否是DTO/VO/Param类型通常包含这些后缀或首字母大写
if (paramType.includes('Dto') || paramType.includes('Vo') || paramType.includes('Param')) {
// 提取纯类型名(去除包名)
const simpleType = paramType.split('.').pop();
dtos.add(simpleType);
} else if (paramType[0] === paramType[0].toUpperCase() && !paramType.startsWith('String') && !paramType.startsWith('Integer') && !paramType.startsWith('Long')) {
// 其他业务类型首字母大写但排除Java基本类型
const simpleType = paramType.split('.').pop();
dtos.add(simpleType);
}
});
}
method.parameters.forEach(param => {
const paramType = param.type;
if (!paramType) {
return;
// 2. 从方法体中提取VO类型通过正则匹配
if (method.methodBody) {
// 匹配 new XxxVo(), XxxVo xxx = ..., List<XxxVo>, etc.
const voMatches = method.methodBody.match(/\b([A-Z]\w*(Vo|Dto|Param))\b/g);
if (voMatches) {
voMatches.forEach(vo => {
// 排除Java基本类型
if (!['String', 'Integer', 'Long', 'Double', 'Float', 'Boolean', 'List', 'Map', 'Set'].includes(vo)) {
dtos.add(vo);
}
});
}
// 检查是否是DTO/VO/Param类型通常包含这些后缀或首字母大写
if (paramType.includes('Dto') || paramType.includes('Vo') || paramType.includes('Param')) {
// 提取纯类型名(去除包名)
const simpleType = paramType.split('.').pop();
dtos.add(simpleType);
} else if (paramType[0] === paramType[0].toUpperCase() && !paramType.startsWith('String') && !paramType.startsWith('Integer') && !paramType.startsWith('Long')) {
// 其他业务类型首字母大写但排除Java基本类型
const simpleType = paramType.split('.').pop();
dtos.add(simpleType);
}
});
}
});
return Array.from(dtos);
@@ -389,8 +402,16 @@ ${methods}
// 添加DTO导入
if (javaService.dtos && javaService.dtos.length > 0) {
javaService.dtos.forEach(dto => {
const dtoName = this.namingUtils.generateDtoName(dto);
const dtoFileName = this.namingUtils.generateFileName(dto, 'dto');
// 清理泛型语法List<String> → List, Record<String, Vo> → Record
let cleanDto = dto.replace(/<[^>]+>/g, '');
// 跳过Java基础类型和集合类型不是DTO
if (['List', 'ArrayList', 'Map', 'HashMap', 'Set', 'HashSet', 'Record', 'String', 'Integer', 'Long', 'Boolean'].includes(cleanDto)) {
return;
}
const dtoName = this.namingUtils.generateDtoName(cleanDto);
const dtoFileName = this.namingUtils.generateFileName(cleanDto, 'dto');
imports.push(`import { ${dtoName} } from '../dtos/${dtoFileName.replace('.ts', '')}';`);
});
}