128 lines
5.7 KiB
Java
128 lines
5.7 KiB
Java
package com.codechecker.analyzer;
|
||
|
||
import com.codechecker.config.AppConfig;
|
||
import com.codechecker.git.GitChangeScanner;
|
||
import com.codechecker.model.ChangedClassFile;
|
||
import com.codechecker.model.ClassChangeKind;
|
||
import com.codechecker.model.ClassChangeReport;
|
||
import com.codechecker.model.FieldChange;
|
||
import com.codechecker.model.FieldInfo;
|
||
import com.codechecker.parser.ClassDeclParser;
|
||
import com.codechecker.parser.ClassFieldParser;
|
||
|
||
import java.io.IOException;
|
||
import java.nio.file.Path;
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
|
||
/**
|
||
* 编排 git 扫描、字段 diff、影响分析,生成待通知的 ClassChangeReport 列表。
|
||
*/
|
||
public class ClassChangeAnalyzer {
|
||
private final GitChangeScanner gitScanner;
|
||
private final ClassFieldParser classFieldParser = new ClassFieldParser();
|
||
private final FieldDiffEngine fieldDiffEngine = new FieldDiffEngine();
|
||
private final ImpactAnalyzer impactAnalyzer = new ImpactAnalyzer();
|
||
private final ClassDeclParser classDeclParser = new ClassDeclParser();
|
||
|
||
public ClassChangeAnalyzer(GitChangeScanner gitScanner) {
|
||
this.gitScanner = gitScanner;
|
||
}
|
||
|
||
/** 扫描变更文件并逐条分析,无实质变更的 MODIFIED 会被跳过 */
|
||
public List<ClassChangeReport> analyze(Path repoRoot, AppConfig config, String oldSha, String newSha,
|
||
Map<String, com.codechecker.model.ApiEndpoint> endpointIndex) throws IOException {
|
||
List<ChangedClassFile> changedFiles = gitScanner.scanChangedClasses(oldSha, newSha);
|
||
List<ClassChangeReport> reports = new ArrayList<>();
|
||
|
||
for (ChangedClassFile changedFile : changedFiles) {
|
||
if (changedFile.getStatus() == ChangedClassFile.ChangeStatus.DELETED) {
|
||
reports.add(analyzeDeleted(changedFile, config, repoRoot, oldSha, endpointIndex));
|
||
continue;
|
||
}
|
||
ClassChangeReport report = analyzeModifiedOrRenamed(changedFile, config, repoRoot, oldSha, newSha, endpointIndex);
|
||
if (report != null) {
|
||
reports.add(report);
|
||
}
|
||
}
|
||
return reports;
|
||
}
|
||
|
||
/** 处理删除:标记 DELETED 并分析影响(基于旧源码) */
|
||
private ClassChangeReport analyzeDeleted(ChangedClassFile changedFile, AppConfig config, Path repoRoot,
|
||
String oldSha, Map<String, com.codechecker.model.ApiEndpoint> endpointIndex)
|
||
throws IOException {
|
||
String path = changedFile.getRelativePath();
|
||
String oldSource = gitScanner.readFileAtCommit(oldSha, path);
|
||
|
||
String classDescription = classDeclParser.extractClassDescription(
|
||
oldSource, changedFile.getClassName());
|
||
|
||
ClassChangeReport report = new ClassChangeReport(
|
||
changedFile.getClassName(),
|
||
null,
|
||
changedFile.getClassType(),
|
||
ClassChangeKind.DELETED,
|
||
path,
|
||
config.isDtoEntityConversionEnabled(),
|
||
classDescription
|
||
);
|
||
impactAnalyzer.analyze(report, endpointIndex, config, repoRoot, oldSource, oldSource);
|
||
return report;
|
||
}
|
||
|
||
/** 处理修改/重命名:字段 diff → 判定 changeKind → 影响分析 */
|
||
private ClassChangeReport analyzeModifiedOrRenamed(ChangedClassFile changedFile, AppConfig config,
|
||
Path repoRoot, String oldSha, String newSha,
|
||
Map<String, com.codechecker.model.ApiEndpoint> endpointIndex)
|
||
throws IOException {
|
||
String oldPath = changedFile.pathForOldCommit();
|
||
String newPath = changedFile.getRelativePath();
|
||
|
||
String oldSource = gitScanner.readFileAtCommit(oldSha, oldPath);
|
||
String newSource = gitScanner.readFileAtCommit(newSha, newPath);
|
||
if (newSource == null || newSource.isBlank()) {
|
||
newSource = gitScanner.readFileAtHead(newPath);
|
||
}
|
||
|
||
String oldFallback = ClassDeclParser.classNameFromPath(oldPath);
|
||
String newFallback = ClassDeclParser.classNameFromPath(newPath);
|
||
String oldClassName = changedFile.getOldClassName() != null
|
||
? changedFile.getOldClassName()
|
||
: classDeclParser.resolveClassName(oldSource, oldFallback);
|
||
String newClassName = classDeclParser.resolveClassName(newSource, newFallback);
|
||
|
||
List<FieldInfo> oldFields = classFieldParser.parseFields(oldSource, oldClassName);
|
||
List<FieldInfo> newFields = classFieldParser.parseFields(newSource, newClassName);
|
||
List<FieldChange> fieldChanges = fieldDiffEngine.diff(oldFields, newFields);
|
||
|
||
boolean renamed = !oldClassName.equals(newClassName);
|
||
ClassChangeKind changeKind;
|
||
if (renamed && fieldChanges.isEmpty()) {
|
||
changeKind = ClassChangeKind.RENAME_ONLY;
|
||
} else if (renamed) {
|
||
changeKind = ClassChangeKind.RENAME_AND_FIELDS;
|
||
} else if (!fieldChanges.isEmpty()) {
|
||
changeKind = ClassChangeKind.FIELDS_ONLY;
|
||
} else {
|
||
return null;
|
||
}
|
||
|
||
String classDescription = classDeclParser.extractClassDescription(newSource, newClassName);
|
||
|
||
ClassChangeReport report = new ClassChangeReport(
|
||
newClassName,
|
||
renamed ? oldClassName : null,
|
||
changedFile.getClassType(),
|
||
changeKind,
|
||
newPath,
|
||
config.isDtoEntityConversionEnabled(),
|
||
classDescription
|
||
);
|
||
fieldChanges.forEach(report::addFieldChange);
|
||
impactAnalyzer.analyze(report, endpointIndex, config, repoRoot, newSource, oldSource);
|
||
return report;
|
||
}
|
||
}
|