roots = new LinkedHashSet<>();
diff --git a/.gitea/checker/src/main/java/com/codechecker/analyzer/ImpactAnalyzer.java b/.gitea/checker/src/main/java/com/codechecker/analyzer/ImpactAnalyzer.java
index 44410d7..f2db5e4 100644
--- a/.gitea/checker/src/main/java/com/codechecker/analyzer/ImpactAnalyzer.java
+++ b/.gitea/checker/src/main/java/com/codechecker/analyzer/ImpactAnalyzer.java
@@ -35,6 +35,8 @@ public class ImpactAnalyzer {
matchEndpoints(report, endpointIndex, matchNames);
}
+ report.setObjectRoleLabels(NestedObjectRoleResolver.resolve(report, nestIndex, endpointIndex));
+
if (!config.isDtoEntityConversionEnabled()) {
return;
}
diff --git a/.gitea/checker/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java b/.gitea/checker/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java
new file mode 100644
index 0000000..0944862
--- /dev/null
+++ b/.gitea/checker/src/main/java/com/codechecker/analyzer/NestedObjectRoleResolver.java
@@ -0,0 +1,54 @@
+package com.codechecker.analyzer;
+
+import com.codechecker.model.ApiEndpoint;
+import com.codechecker.model.ClassChangeReport;
+import com.codechecker.model.ClassType;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 判定 Dto/Vo 在类变更通知中的对象角色标签(方案 B:嵌套 + 可选顶层)。
+ *
+ * 仅当存在嵌套祖先时标注;纯顶层不标注;既嵌套又直接作接口根类型时同时标注。
+ */
+public final class NestedObjectRoleResolver {
+
+ private NestedObjectRoleResolver() {
+ }
+
+ public static List resolve(ClassChangeReport report, DtoNestIndex nestIndex,
+ Map endpointIndex) {
+ if (report.getClassType() != ClassType.DTO && report.getClassType() != ClassType.VO) {
+ return List.of();
+ }
+ if (nestIndex == null) {
+ return List.of();
+ }
+ String className = report.getClassName();
+ if (!nestIndex.hasAncestors(className)) {
+ return List.of();
+ }
+ List labels = new ArrayList<>();
+ labels.add("嵌套对象");
+ if (isDirectEndpointType(className, endpointIndex)) {
+ labels.add("顶层对象");
+ }
+ return List.copyOf(labels);
+ }
+
+ /** 是否直接出现在接口入参或返回值类型(非仅经祖先传播) */
+ private static boolean isDirectEndpointType(String className, Map endpointIndex) {
+ if (className == null || className.isBlank() || endpointIndex == null) {
+ return false;
+ }
+ for (ApiEndpoint endpoint : endpointIndex.values()) {
+ if (endpoint.getParamTypes().contains(className)
+ || endpoint.getReturnTypes().contains(className)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/.gitea/checker/src/main/java/com/codechecker/model/ClassChangeReport.java b/.gitea/checker/src/main/java/com/codechecker/model/ClassChangeReport.java
index 1dfc0d7..195632b 100644
--- a/.gitea/checker/src/main/java/com/codechecker/model/ClassChangeReport.java
+++ b/.gitea/checker/src/main/java/com/codechecker/model/ClassChangeReport.java
@@ -18,6 +18,7 @@ public class ClassChangeReport {
private final List conversionEntities = new ArrayList<>();
private final List frontendImpactEndpoints = new ArrayList<>();
private final boolean conversionCheckEnabled;
+ private List objectRoleLabels = List.of();
public ClassChangeReport(String className, String oldClassName, ClassType classType,
ClassChangeKind changeKind, String sourceFile,
@@ -98,6 +99,15 @@ public class ClassChangeReport {
return conversionCheckEnabled;
}
+ /** 对象角色标签(如「嵌套对象」「顶层对象」),仅 Dto/Vo 且存在嵌套时非空 */
+ public List getObjectRoleLabels() {
+ return objectRoleLabels;
+ }
+
+ public void setObjectRoleLabels(List labels) {
+ this.objectRoleLabels = labels == null || labels.isEmpty() ? List.of() : List.copyOf(labels);
+ }
+
/** 追加一条字段变更 */
public void addFieldChange(FieldChange change) {
fieldChanges.add(change);
diff --git a/.gitea/checker/src/main/java/com/codechecker/notify/WeComNotifier.java b/.gitea/checker/src/main/java/com/codechecker/notify/WeComNotifier.java
index c023610..e5085fa 100644
--- a/.gitea/checker/src/main/java/com/codechecker/notify/WeComNotifier.java
+++ b/.gitea/checker/src/main/java/com/codechecker/notify/WeComNotifier.java
@@ -82,14 +82,17 @@ public class WeComNotifier {
return truncate(sb.toString());
}
- /** 变更对象行:类名(绿)+ 可选中文说明(灰,整行加粗) */
+ /** 变更对象行:类名(绿)+ 可选中文说明 + 嵌套角色标签(灰,整行加粗) */
private String formatChangeTarget(ClassChangeReport report) {
- String name = colorInfo(safe(report.getClassName()));
+ StringBuilder line = new StringBuilder(colorInfo(safe(report.getClassName())));
String description = report.getClassDescription();
- if (description == null || description.isBlank()) {
- return name;
+ if (description != null && !description.isBlank()) {
+ line.append("(").append(colorComment(description)).append(")");
}
- return name + "(" + colorComment(description) + ")";
+ for (String role : report.getObjectRoleLabels()) {
+ line.append("(").append(colorComment(role)).append(")");
+ }
+ return line.toString();
}
/** 头部元信息,每项一行引用(加粗) */
@@ -139,14 +142,12 @@ public class WeComNotifier {
appendImpactByType(sb, report);
}
- /** Dto/Vo/Entity/Model 各展示不同的 request/response/转换段落 */
+ /** Dto/Vo 均展示 request + response(二者可能交叉);Entity/Model 仅类转换 */
private void appendImpactByType(StringBuilder sb, ClassChangeReport report) {
switch (report.getClassType()) {
case DTO:
- appendSectionIfNeeded(sb, report, true, false, true);
- break;
case VO:
- appendSectionIfNeeded(sb, report, false, true, true);
+ appendSectionIfNeeded(sb, report, true, true, true);
break;
case ENTITY:
case MODEL:
@@ -170,7 +171,7 @@ public class WeComNotifier {
appendEndpointList(sb, report.getFrontendImpactEndpoints());
sb.append("\n");
}
- if (showConversion) {
+ if (showConversion && report.isConversionCheckEnabled()) {
sb.append("### 类转换影响").append("\n");
appendConversionList(sb, report);
}
@@ -178,10 +179,6 @@ public class WeComNotifier {
/** 渲染关联 Entity,每项一行 */
private void appendConversionList(StringBuilder sb, ClassChangeReport report) {
- if (!report.isConversionCheckEnabled()) {
- sb.append(quoteLine(colorComment("未开启检测"))).append("\n");
- return;
- }
if (report.getConversionEntities().isEmpty()) {
sb.append(quoteLine(colorComment("无"))).append("\n");
return;