Files
codeCheck/src/main/java/com/codechecker/api/analyzer/ParameterDiffEngine.java
2026-06-09 17:57:52 +08:00

235 lines
10 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}