first commit

This commit is contained in:
2026-06-09 17:57:52 +08:00
parent 518484015e
commit be9ace256d
46 changed files with 5533 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
package com.codechecker.api.analyzer;
import com.codechecker.analyzer.FieldDiffEngine;
import com.codechecker.api.model.EndpointSnapshot;
import com.codechecker.api.model.MethodParameterSnapshot;
import com.codechecker.api.model.ParameterChange;
import com.codechecker.api.parser.NestedDtoFieldParser;
import com.codechecker.api.parser.NestedFieldInfo;
import com.codechecker.git.GitChangeScanner;
import com.codechecker.model.FieldChange;
import com.codechecker.model.FieldInfo;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 接口入参 diff普通参数 + RequestBody 嵌套 Dto 字段)。
*
* path/query 规则:
* - 形参名+类型相同,仅绑定名变 → 重命名
* - 形参名+绑定名相同,仅类型变 → 类型变更
* - 仅形参名变(绑定名不变)→ 不通知
* - 类型与绑定名同时变,或三者都变 → 先删除后新增
*/
public class ParameterDiffEngine {
private final NestedDtoFieldParser nestedDtoFieldParser;
private final FieldDiffEngine fieldDiffEngine = new FieldDiffEngine();
public ParameterDiffEngine(Path repoRoot, List<String> searchDirs,
GitChangeScanner gitScanner, String oldSha, String newSha, int maxDepth) {
this.nestedDtoFieldParser = new NestedDtoFieldParser(repoRoot, searchDirs, gitScanner, oldSha, newSha, maxDepth);
}
public List<ParameterChange> diff(EndpointSnapshot oldSnap, EndpointSnapshot newSnap) throws IOException {
List<ParameterChange> changes = new ArrayList<>();
changes.addAll(diffBodyParams(oldSnap, newSnap));
changes.addAll(diffBindingParams(oldSnap, newSnap));
return changes;
}
private List<ParameterChange> diffBodyParams(EndpointSnapshot oldSnap, EndpointSnapshot newSnap)
throws IOException {
Map<String, MethodParameterSnapshot> oldParams = filterBySource(oldSnap, "body");
Map<String, MethodParameterSnapshot> newParams = filterBySource(newSnap, "body");
List<ParameterChange> changes = new ArrayList<>();
for (Map.Entry<String, MethodParameterSnapshot> entry : newParams.entrySet()) {
MethodParameterSnapshot oldParam = oldParams.get(entry.getKey());
MethodParameterSnapshot newParam = entry.getValue();
if (oldParam == null) {
changes.addAll(addedBodyChanges(newParam));
} else {
changes.addAll(diffBodyDto(oldParam, newParam));
}
}
for (Map.Entry<String, MethodParameterSnapshot> entry : oldParams.entrySet()) {
if (!newParams.containsKey(entry.getKey())) {
changes.addAll(removedBodyChanges(entry.getValue()));
}
}
return changes;
}
private List<ParameterChange> diffBindingParams(EndpointSnapshot oldSnap, EndpointSnapshot newSnap) {
Map<String, MethodParameterSnapshot> oldParams = filterBindingParams(oldSnap);
Map<String, MethodParameterSnapshot> newParams = filterBindingParams(newSnap);
List<ParameterChange> changes = new ArrayList<>();
List<MethodParameterSnapshot> unmatchedOld = new ArrayList<>();
List<MethodParameterSnapshot> unmatchedNew = new ArrayList<>();
for (Map.Entry<String, MethodParameterSnapshot> entry : newParams.entrySet()) {
MethodParameterSnapshot oldParam = oldParams.get(entry.getKey());
MethodParameterSnapshot newParam = entry.getValue();
if (oldParam == null) {
unmatchedNew.add(newParam);
} else if (!oldParam.getType().equals(newParam.getType())) {
changes.add(ParameterChange.modified(
newParam.displayName(),
newParam.getType(),
newParam.getDescription(),
oldParam.getType() + "" + newParam.getType(),
newParam.getSource(),
null, null, null));
}
}
for (Map.Entry<String, MethodParameterSnapshot> entry : oldParams.entrySet()) {
if (!newParams.containsKey(entry.getKey())) {
unmatchedOld.add(entry.getValue());
}
}
pairRenamedBindingParams(unmatchedOld, unmatchedNew, changes);
for (MethodParameterSnapshot removed : unmatchedOld) {
changes.add(ParameterChange.removed(
removed.displayName(), removed.getType(), removed.getDescription(),
removed.getSource(), null, null, null));
}
for (MethodParameterSnapshot added : unmatchedNew) {
changes.add(ParameterChange.added(
added.displayName(), added.getType(), added.getDescription(),
added.getSource(), null, null, null));
}
return changes;
}
/** 形参名+类型相同,仅绑定名变化 → 重命名 */
private void pairRenamedBindingParams(List<MethodParameterSnapshot> unmatchedOld,
List<MethodParameterSnapshot> unmatchedNew,
List<ParameterChange> changes) {
Set<MethodParameterSnapshot> pairedOld = new HashSet<>();
Set<MethodParameterSnapshot> pairedNew = new HashSet<>();
for (MethodParameterSnapshot oldParam : unmatchedOld) {
if (pairedOld.contains(oldParam)) {
continue;
}
for (MethodParameterSnapshot newParam : unmatchedNew) {
if (pairedNew.contains(newParam)) {
continue;
}
if (!oldParam.getSource().equals(newParam.getSource())) {
continue;
}
if (!oldParam.getName().equals(newParam.getName())) {
continue;
}
if (!oldParam.getType().equals(newParam.getType())) {
continue;
}
changes.add(ParameterChange.renamed(
oldParam.getBindingName(),
newParam.getBindingName(),
newParam.getType(),
newParam.getDescription(),
newParam.getSource(),
null, null, null));
pairedOld.add(oldParam);
pairedNew.add(newParam);
break;
}
}
unmatchedOld.removeIf(pairedOld::contains);
unmatchedNew.removeIf(pairedNew::contains);
}
private Map<String, MethodParameterSnapshot> filterBindingParams(EndpointSnapshot snap) {
Map<String, MethodParameterSnapshot> map = new LinkedHashMap<>();
for (MethodParameterSnapshot p : snap.getParameters()) {
if (isBindingParam(p)) {
map.put(p.identityKey(), p);
}
}
return map;
}
private Map<String, MethodParameterSnapshot> filterBySource(EndpointSnapshot snap, String source) {
Map<String, MethodParameterSnapshot> map = new LinkedHashMap<>();
for (MethodParameterSnapshot p : snap.getParameters()) {
if (source.equals(p.getSource())) {
map.put(p.identityKey(), p);
}
}
return map;
}
private boolean isBindingParam(MethodParameterSnapshot param) {
return "path".equals(param.getSource()) || "query".equals(param.getSource());
}
private List<ParameterChange> diffBodyDto(MethodParameterSnapshot oldParam,
MethodParameterSnapshot newParam) throws IOException {
List<NestedFieldInfo> oldFields = nestedDtoFieldParser.parseNestedFieldsAtOldCommit(oldParam.getDtoClassName());
List<NestedFieldInfo> newFields = nestedDtoFieldParser.parseNestedFieldsAtNewCommit(newParam.getDtoClassName());
List<FieldChange> fieldChanges = fieldDiffEngine.diff(toFieldInfo(oldFields), toFieldInfo(newFields));
List<ParameterChange> result = new ArrayList<>();
for (FieldChange fc : fieldChanges) {
result.add(mapFieldChange(fc, newParam.getName(), newParam.getDtoClassName()));
}
return result;
}
private ParameterChange mapFieldChange(FieldChange fc, String bodyParamName, String dtoName) {
String path = fc.getFieldName();
switch (fc.getKind()) {
case ADDED:
return ParameterChange.added(path, fc.getNewType(), fc.getDescription(),
"body", bodyParamName, dtoName, path);
case REMOVED:
return ParameterChange.removed(path, fc.getOldType(), fc.getDescription(),
"body", bodyParamName, dtoName, path);
case RENAMED:
return ParameterChange.renamed(fc.getOldFieldName(), fc.getFieldName(),
fc.getNewType(), fc.getDescription(), "body", bodyParamName, dtoName, path);
case MODIFIED:
default:
return ParameterChange.modified(path, fc.getNewType(), fc.getDescription(),
fc.getDetail(), "body", bodyParamName, dtoName, path);
}
}
private List<ParameterChange> addedBodyChanges(MethodParameterSnapshot param) throws IOException {
List<ParameterChange> list = new ArrayList<>();
for (NestedFieldInfo field : nestedDtoFieldParser.parseNestedFieldsAtNewCommit(param.getDtoClassName())) {
list.add(ParameterChange.added(field.getPath(), field.getType(), field.getDescription(),
"body", param.getName(), param.getDtoClassName(), field.getPath()));
}
return list;
}
private List<ParameterChange> removedBodyChanges(MethodParameterSnapshot param) throws IOException {
List<ParameterChange> list = new ArrayList<>();
for (NestedFieldInfo field : nestedDtoFieldParser.parseNestedFieldsAtOldCommit(param.getDtoClassName())) {
list.add(ParameterChange.removed(field.getPath(), field.getType(), field.getDescription(),
"body", param.getName(), param.getDtoClassName(), field.getPath()));
}
return list;
}
private List<FieldInfo> toFieldInfo(List<NestedFieldInfo> nested) {
List<FieldInfo> result = new ArrayList<>();
for (NestedFieldInfo info : nested) {
result.add(new FieldInfo(info.getPath(), info.getType(), info.getDescription()));
}
return result;
}
}