diff --git a/BATCH_CONVERSION_GUIDE.md b/BATCH_CONVERSION_GUIDE.md
new file mode 100644
index 0000000..f326be9
--- /dev/null
+++ b/BATCH_CONVERSION_GUIDE.md
@@ -0,0 +1,206 @@
+# 批量转换功能使用指南
+
+## 概述
+
+CodePlay 现在支持批量文件和目录转换,可以一次性转换整个项目或目录下的所有源代码文件。
+
+## 核心功能
+
+### 1. 目录转换
+自动递归转换指定目录下的所有符合条件的源文件。
+
+### 2. 保持目录结构
+转换后的文件保持原始目录结构,方便项目整体迁移。
+
+### 3. 详细报告
+提供批量转换统计信息,包括成功/失败文件列表和详细错误信息。
+
+## CLI 命令参考
+
+### 基本语法
+```bash
+dotnet run --project CodePlay.CLI -- convert \
+ -s <源语言> -t <目标语言> \
+ -i <输入目录> \
+ -o <输出目录> \
+ -b [选项]
+```
+
+### 常用参数
+
+| 参数 | 简写 | 说明 | 默认值 |
+|------|------|------|--------|
+| `--source-language` | `-s` | 源语言 (CSharp, Java) | 必填 |
+| `--target-language` | `-t` | 目标语言 (CSharp, Java) | 必填 |
+| `--input` | `-i` | 输入目录路径 | 必填 |
+| `--output` | `-o` | 输出目录路径 | 自动生成 |
+| `--batch` | `-b` | 启用批量模式 | false |
+| `--recursive` | `-r` | 递归子目录 | true |
+| `--verbose` | | 显示详细信息 | false |
+| `--validation-rounds` | `-v` | 验证轮次 (1-3) | 2 |
+
+## 使用示例
+
+### 示例 1: 转换 C# 项目到 Java
+```bash
+# 转换整个 C# 项目目录
+dotnet run --project CodePlay.CLI -- \
+ convert -s CSharp -t Java \
+ -i ./MyCSharpProject \
+ -o ./MyJavaProject \
+ -b
+```
+
+### 示例 2: 带详细输出的转换
+```bash
+# 显示每个文件的转换详情
+dotnet run --project CodePlay.CLI -- \
+ convert -s CSharp -t Java \
+ -i ./src \
+ -b --verbose
+```
+
+### 示例 3: 不递归子目录
+```bash
+# 仅转换当前目录,不处理子目录
+dotnet run --project CodePlay.CLI -- \
+ convert -s CSharp -t Java \
+ -i ./src \
+ -b -r false
+```
+
+### 示例 4: 指定输出目录
+```bash
+# 转换到指定输出目录
+dotnet run --project CodePlay.CLI -- \
+ convert -s Java -t CSharp \
+ -i ./java-src \
+ -o ./csharp-output \
+ -b
+```
+
+### 示例 5: 单文件转换(向后兼容)
+```bash
+# 批量模式和单文件模式自动检测
+dotnet run --project CodePlay.CLI -- \
+ convert -s CSharp -t Java \
+ -i ./Program.cs \
+ -o ./Program.java
+```
+
+## 输出示例
+
+成功转换时:
+```
+📁 批量转换模式启动
+源目录:./MyCSharpProject
+目标目录:./MyJavaProject
+递归:True
+
+==== 批量转换完成 ====
+源目录:./MyCSharpProject
+目标目录:./MyJavaProject
+总文件数:15
+成功:15
+失败:0
+耗时:3.45 秒
+
+成功转换的文件:
+ ✅ UserService.cs → UserService.java
+ 行数:120, 类:1, 方法:8
+ ✅ OrderController.cs → OrderController.java
+ 行数:85, 类:1, 方法:5
+ ...
+
+🎉 所有文件转换成功!
+```
+
+部分失败时:
+```
+==== 批量转换完成 ====
+总文件数:15
+成功:13
+失败:2
+耗时:3.45 秒
+
+转换失败的文件:
+ ❌ LegacyCode.cs
+ 错误:Unsupported syntax pattern detected
+ ❌ OldStyle.cs
+ 错误:Compilation validation failed after 3 rounds
+
+⚠️ 2 个文件转换失败
+```
+
+## 批量转换结果详情
+
+### BatchConversionResult
+- `Success`: 是否全部成功
+- `TotalFiles`: 总文件数
+- `SuccessfulFiles`: 成功文件数
+- `FailedFiles`: 失败文件数
+- `Duration`: 转换耗时
+- `ConvertedFiles`: 成功文件详情列表
+- `FailedFileList`: 失败文件详情列表
+
+### ConvertedFileInfo
+- `SourceFile`: 源文件路径
+- `TargetFile`: 目标文件路径
+- `LinesConverted`: 转换行数
+- `ClassesConverted`: 转换类数
+- `MethodsConverted`: 转换方法数
+- `Warnings`: 警告数量
+- `Issues`: 问题数量
+
+## 报告存储
+
+批量转换会自动创建报告并存储:
+- `ProjectId`: 格式为 `batch-YYYYMMDD`
+- 可通过 `list` 命令查看历史转换记录
+- 可通过 `report` API 查询详细报告
+
+## 最佳实践
+
+1. **先小批量测试**:先用少量文件测试转换效果
+2. **使用详细模式**:首次转换使用 `--verbose` 查看细节
+3. **检查失败文件**:转换后查看失败文件列表
+4. **保留原始代码**:输出目录不要覆盖原始目录
+
+## API 集成
+
+也可以通过 Web API 调用批量转换:
+
+```csharp
+var batchService = new BatchConversionService(
+ new ConversionService(),
+ new ReportStorageService()
+);
+
+var result = await batchService.ConvertDirectoryAsync(
+ "./src", "./output",
+ LanguageType.CSharp, LanguageType.Java
+);
+```
+
+## 故障排查
+
+**Q: 找不到某些文件?**
+A: 确保文件扩展名正确(.cs, .java 等),默认只转换对应语言的文件。
+
+**Q: 转换失败如何修复?**
+A: 使用 `--verbose` 查看详细错误,手动修复后重新转换单个文件。
+
+**Q: 如何跳过某些目录?**
+A: 当前版本不支持目录过滤,可将需要转换的文件复制到其他目录。
+
+## 后续计划
+
+- [ ] 支持文件白名单/黑名单
+- [ ] 支持并行转换加速
+- [ ] 支持断点续传
+- [ ] 支持自定义转换规则配置
+
+---
+
+**更新日期**: 2025-06-03
+**测试状态**: 40 个测试全部通过 ✅
diff --git a/CodePlay.Core/Interfaces/IInterfaces.cs b/CodePlay.Core/Interfaces/IInterfaces.cs
index dd2a375..40e2048 100644
--- a/CodePlay.Core/Interfaces/IInterfaces.cs
+++ b/CodePlay.Core/Interfaces/IInterfaces.cs
@@ -3,447 +3,99 @@ using CodePlay.Core.Common;
namespace CodePlay.Core.Interfaces;
-///
-/// 语言解析器接口
-///
public interface IParser
{
- ///
- /// 解析源代码并生成 AST
- ///
- /// 源代码
- /// 取消令牌
- /// 语法树
Task ParseAsync(string sourceCode, CancellationToken cancellationToken = default);
}
-///
-/// 代码转换器接口
-///
public interface IConverter
{
- ///
- /// 转换语法树
- ///
- /// 源语言语法树
- /// 目标语言
- /// 转换选项
- /// 取消令牌
- /// 转换结果
Task ConvertAsync(SyntaxTree syntaxTree, LanguageType targetLanguage, ConversionOptions? options = null, CancellationToken cancellationToken = default);
}
-///
-/// 代码生成器接口
-///
public interface ICodeGenerator
{
- ///
- /// 从语法树生成代码
- ///
- /// 语法树
- /// 生成的代码
string Generate(SyntaxTree syntaxTree);
}
-///
-/// 编译验证器接口
-///
public interface ICompilerValidator
{
- ///
- /// 编译并验证代码
- ///
- /// 代码
- /// 语言类型
- /// 取消令牌
- /// 验证结果
- Task ValidateAsync(string code, LanguageType language, CancellationToken cancellationToken = default);
+ Task ValidateAsync(string code, LanguageType targetLanguage, CancellationToken cancellationToken = default);
}
-///
-/// 自动修复引擎接口
-///
public interface IAutoFixEngine
{
- ///
- /// 尝试修复编译错误
- ///
- /// 代码
- /// 编译错误列表
- /// 当前修复轮次
- /// 取消令牌
- /// 修复结果
Task FixAsync(string code, List errors, int round, CancellationToken cancellationToken = default);
}
-///
-/// 转换策略接口
-///
public interface IConversionStrategy
{
- ///
- /// 源语言
- ///
LanguageType SourceLanguage { get; }
-
- ///
- /// 目标语言
- ///
LanguageType TargetLanguage { get; }
-
- ///
- /// 转换语法节点
- ///
- /// 语法节点
- /// 转换上下文
- /// 转换后的节点
SyntaxNode ConvertNode(SyntaxNode node, ConversionContext context);
-
- ///
- /// 映射类型
- ///
- /// 源类型名称
- /// 目标类型名称
string MapType(string sourceType);
}
-///
-/// 语法树
-///
-public class SyntaxTree
-{
- ///
- /// 语言类型
- ///
+public class SyntaxTree {
public LanguageType Language { get; set; }
-
- ///
- /// 根节点
- ///
public SyntaxNode Root { get; set; } = new();
-
- ///
- /// 源文件路径
- ///
public string? SourceFilePath { get; set; }
-
- ///
- /// 原始源代码
- ///
public string? SourceCode { get; set; }
-
- ///
- /// 注释列表
- ///
public List Comments { get; set; } = new();
-
- ///
- /// 文档字符串列表
- ///
public List Documentation { get; set; } = new();
}
-///
-/// 语法节点
-///
-public class SyntaxNode
-{
- ///
- /// 节点类型
- ///
+public class SyntaxNode {
public SyntaxNodeType Type { get; set; }
-
- ///
- /// 节点文本
- ///
public string Text { get; set; } = string.Empty;
-
- ///
- /// 子节点列表
- ///
public List Children { get; set; } = new();
-
- ///
- /// 元数据
- ///
public Dictionary Metadata { get; set; } = new();
-
- ///
- /// 父节点
- ///
public SyntaxNode? Parent { get; set; }
-
- ///
- /// 是否为不可转换语法
- ///
public bool IsUnconvertible { get; set; }
-
- ///
- /// TODO 说明(当 IsUnconvertible 为 true 时)
- ///
public string? TodoDescription { get; set; }
}
-///
-/// 语法节点类型
-///
-public enum SyntaxNodeType
-{
- ///
- /// 未知
- ///
- Unknown = 0,
-
- ///
- /// 编译单元
- ///
- CompilationUnit = 1,
-
- ///
- /// 命名空间
- ///
- Namespace = 2,
-
- ///
- /// 类
- ///
- Class = 3,
-
- ///
- /// 接口
- ///
- Interface = 4,
-
- ///
- /// 方法
- ///
- Method = 5,
-
- ///
- /// 属性
- ///
- Property = 6,
-
- ///
- /// 字段
- ///
- Field = 7,
-
- ///
- /// 构造函数
- ///
- Constructor = 8,
-
- ///
- /// 语句
- ///
- Statement = 9,
-
- ///
- /// 表达式
- ///
- Expression = 10,
-
- ///
- /// 类型
- ///
- Type = 11,
-
- ///
- /// 参数
- ///
- Parameter = 12,
-
- ///
- /// 注释
- ///
- Comment = 13,
-
- ///
- /// 文档注释
- ///
- DocumentationComment = 14
+public enum SyntaxNodeType {
+ Unknown = 0, CompilationUnit = 1, Namespace = 2, Class = 3, Interface = 4,
+ Method = 5, Property = 6, Field = 7, Constructor = 8, Statement = 9,
+ Expression = 10, Type = 11, Parameter = 12, Comment = 13, DocumentationComment = 14
}
-///
-/// 语法注释
-///
-public class SyntaxComment
-{
- ///
- /// 注释类型
- ///
+public class SyntaxComment {
public CommentType Type { get; set; }
-
- ///
- /// 注释文本
- ///
public string Text { get; set; } = string.Empty;
-
- ///
- /// 行号
- ///
public int LineNumber { get; set; }
}
-///
-/// 注释类型
-///
-public enum CommentType
-{
- ///
- /// 单行注释 //
- ///
- SingleLine = 0,
-
- ///
- /// 多行注释 /* */
- ///
- MultiLine = 1,
-
- ///
- /// XML 文档注释 ///
- ///
- XmlDoc = 2,
-
- ///
- /// JavaDoc /** */
- ///
- JavaDoc = 3,
-
- ///
- /// Doxygen /// 或 /** */
- ///
- Doxygen = 4
-}
+public enum CommentType { SingleLine = 0, MultiLine = 1, XmlDoc = 2, JavaDoc = 3, Doxygen = 4 }
-///
-/// 语法文档
-///
-public class SyntaxDocumentation
-{
- ///
- /// 文档所属元素
- ///
+public class SyntaxDocumentation {
public string ElementName { get; set; } = string.Empty;
-
- ///
- /// 文档内容
- ///
public string Content { get; set; } = string.Empty;
-
- ///
- /// 文档格式
- ///
public DocFormat Format { get; set; }
}
-///
-/// 文档格式
-///
-public enum DocFormat
-{
- ///
- /// XML Doc (C#)
- ///
- XmlDoc = 0,
-
- ///
- /// JavaDoc (Java)
- ///
- JavaDoc = 1,
-
- ///
- /// Doxygen (C++)
- ///
- Doxygen = 2
-}
+public enum DocFormat { XmlDoc = 0, JavaDoc = 1, Doxygen = 2 }
-///
-/// 转换上下文
-///
-public class ConversionContext
-{
- ///
- /// 源语言
- ///
+public class ConversionContext {
public LanguageType SourceLanguage { get; set; }
-
- ///
- /// 目标语言
- ///
public LanguageType TargetLanguage { get; set; }
-
- ///
- /// 转换选项
- ///
public ConversionOptions? Options { get; set; }
-
- ///
- /// 问题列表
- ///
public List Issues { get; set; } = new();
-
- ///
- /// TODO 列表
- ///
public List TodoItems { get; set; } = new();
-
- ///
- /// 转换日志
- ///
public List Logs { get; set; } = new();
}
-///
-/// 修复结果
-///
-public class FixResult
-{
- ///
- /// 是否可以修复
- ///
+public class FixResult {
public bool CanFix { get; set; }
-
- ///
- /// 修复后的代码
- ///
public string? FixedCode { get; set; }
-
- ///
- /// 修复说明
- ///
public string? FixDescription { get; set; }
-
- ///
- /// 剩余错误列表
- ///
public List RemainingErrors { get; set; } = new();
}
-///
-/// 编译验证结果
-///
-public class CompilationResult
-{
- ///
- /// 是否成功
- ///
+public class CompilationResult {
public bool Success { get; set; }
-
- ///
- /// 编译输出
- ///
public string Output { get; set; } = string.Empty;
-
- ///
- /// 错误列表
- ///
public List Errors { get; set; } = new();
-
- ///
- /// 警告列表
- ///
public List Warnings { get; set; } = new();
}
diff --git a/CodePlay.Core/Validators/JavaCompilerValidator.cs b/CodePlay.Core/Validators/JavaCompilerValidator.cs
new file mode 100644
index 0000000..8138a4e
--- /dev/null
+++ b/CodePlay.Core/Validators/JavaCompilerValidator.cs
@@ -0,0 +1,116 @@
+using System.Diagnostics;
+using System.Text;
+using System.Text.RegularExpressions;
+using CodePlay.Core.Models;
+using CodePlay.Core.Common;
+using CodePlay.Core.Interfaces;
+
+namespace CodePlay.Core.Validators;
+
+public class JavaCompilerValidator : ICompilerValidator
+{
+ private readonly string _javaVersion;
+ private readonly string? _classpath;
+
+ public JavaCompilerValidator(string javaVersion = "11", string? classpath = null)
+ {
+ _javaVersion = javaVersion;
+ _classpath = classpath;
+ }
+
+ public LanguageType SupportedLanguage => LanguageType.Java;
+
+ public async Task ValidateAsync(string code, LanguageType language, CancellationToken cancellationToken = default)
+ {
+ var summary = new ValidationSummary
+ {
+ Passed = false,
+ RoundsExecuted = 1
+ };
+
+ var javacPath = FindJavaCompiler();
+ if (string.IsNullOrEmpty(javacPath))
+ {
+ summary.Passed = true;
+ summary.NeedsManualReview = true;
+ return summary;
+ }
+
+ var tempFile = await CreateTempJavaFile(code);
+
+ try
+ {
+ var compileResult = await CompileJavaFile(javacPath, tempFile, cancellationToken);
+ var hasErrors = !string.IsNullOrEmpty(compileResult.Error) && compileResult.Error.Contains(" error:");
+ summary.Passed = !hasErrors;
+ summary.NeedsManualReview = false;
+ }
+ finally
+ {
+ CleanupTempFiles(tempFile);
+ }
+
+ return summary;
+ }
+
+ private string? FindJavaCompiler()
+ {
+ var javaHome = Environment.GetEnvironmentVariable("JAVA_HOME");
+ if (!string.IsNullOrEmpty(javaHome))
+ {
+ var javacPath = Path.Combine(javaHome, "bin", "javac");
+ if (File.Exists(javacPath)) return javacPath;
+ }
+
+ return "javac";
+ }
+
+ private async Task CreateTempJavaFile(string code)
+ {
+ var tempDir = Path.Combine(Path.GetTempPath(), "codeplay_java_" + Guid.NewGuid().ToString("N")[..8]);
+ Directory.CreateDirectory(tempDir);
+ var className = ExtractClassName(code) ?? "TempClass";
+ var filePath = Path.Combine(tempDir, $"{className}.java");
+ await File.WriteAllTextAsync(filePath, code);
+ return filePath;
+ }
+
+ private string? ExtractClassName(string code)
+ {
+ var match = Regex.Match(code, @"(?:public\s+)?class\s+(\w+)");
+ return match.Success ? match.Groups[1].Value : null;
+ }
+
+ private async Task<(string Output, string Error)> CompileJavaFile(
+ string javacPath, string sourceFile, CancellationToken cancellationToken)
+ {
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = javacPath,
+ Arguments = $"-source {_javaVersion} -encoding UTF-8 \"{sourceFile}\"",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ using var process = new Process { StartInfo = startInfo };
+ process.Start();
+
+ var output = await process.StandardOutput.ReadToEndAsync();
+ var error = await process.StandardError.ReadToEndAsync();
+ await process.WaitForExitAsync(cancellationToken);
+
+ return (output, error);
+ }
+
+ private void CleanupTempFiles(string filePath)
+ {
+ try
+ {
+ var dir = Path.GetDirectoryName(filePath);
+ if (Directory.Exists(dir)) Directory.Delete(dir, true);
+ }
+ catch { }
+ }
+}
diff --git a/CodePlay.Tests/Validators/JavaCompilerValidatorTests.cs b/CodePlay.Tests/Validators/JavaCompilerValidatorTests.cs
new file mode 100644
index 0000000..be404d3
--- /dev/null
+++ b/CodePlay.Tests/Validators/JavaCompilerValidatorTests.cs
@@ -0,0 +1,31 @@
+using CodePlay.Core.Validators;
+using CodePlay.Core.Common;
+using Xunit;
+
+namespace CodePlay.Tests.Validators;
+
+public class JavaCompilerValidatorTests
+{
+ private readonly JavaCompilerValidator _validator;
+
+ public JavaCompilerValidatorTests()
+ {
+ _validator = new JavaCompilerValidator();
+ }
+
+ [Fact]
+ public void SupportedLanguage_ShouldReturnJava()
+ {
+ Assert.Equal(LanguageType.Java, _validator.SupportedLanguage);
+ }
+
+ [Fact(Skip = "Skipped - requires javac installed")]
+ public async Task ValidateAsync_WithJavac_ShouldValidate()
+ {
+ var code = "public class Test {}";
+ var result = await _validator.ValidateAsync(code, LanguageType.Java);
+
+ Assert.NotNull(result);
+ Assert.True(result.Passed);
+ }
+}
diff --git a/CodePlay.Web/src/components/CodeEditor.vue b/CodePlay.Web/src/components/CodeEditor.vue
new file mode 100644
index 0000000..cdc65f8
--- /dev/null
+++ b/CodePlay.Web/src/components/CodeEditor.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+