diff --git a/src/main/java/com/codechecker/cache/report/ReportBuilder.java b/src/main/java/com/codechecker/cache/report/ReportBuilder.java
index 1dc21c7..c8e1610 100644
--- a/src/main/java/com/codechecker/cache/report/ReportBuilder.java
+++ b/src/main/java/com/codechecker/cache/report/ReportBuilder.java
@@ -1,5 +1,6 @@
package com.codechecker.cache.report;
+import com.codechecker.cache.diff.ChangeType;
import com.codechecker.cache.diff.SchemaChange;
import com.codechecker.cache.diff.Severity;
@@ -14,7 +15,7 @@ import java.util.Set;
/**
* 将 {@link CheckReport} 渲染为企微 Markdown / 控制台文本。
*
- * - 企微:按 key 展示位置/类型/序列化骨架变更,不含字段明细,不分 P0/P1/P2
+ * - 企微:按 key 展示位置/类型/序列化骨架变更;类型变更另附摘要行,不分 P0/P1/P2
* - 未解析 key 展示源码表达式 + 灰色「key 无法解析」提示
* - 多 key 优先拼成一条;超过企微上限则按 key 拆成多条
* - CI:先打字段明细,再完整输出企微 Markdown(拆分后的每条)
@@ -129,22 +130,84 @@ public class ReportBuilder {
String newJson = nvl(kc.getNewSkeletonJson());
Set oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
Set newGreen = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
- Set newOrange = SkeletonAnnotator.pathsForTypeChanged(kc.getFieldDetails());
+ Set typeGray = SkeletonAnnotator.pathsForTypeChanged(kc.getFieldDetails());
String oldRendered = oldJson.isEmpty()
- ? "" : "“" + SkeletonAnnotator.annotateOldForWecom(oldJson, oldHighlight) + "”";
+ ? "" : "“" + SkeletonAnnotator.annotateOldForWecom(oldJson, oldHighlight, typeGray) + "”";
String newRendered = newJson.isEmpty()
- ? "" : "“" + SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, newOrange) + "”";
+ ? "" : "“" + SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, typeGray) + "”";
if (oldJson.isEmpty() && !newJson.isEmpty()) {
- sb.append(" > **value 新增为:** ").append(newRendered).append("\n\n");
+ sb.append(" > **value 新增为:** ").append(newRendered).append('\n');
} else if (!oldJson.isEmpty() && newJson.isEmpty()) {
- sb.append(" > **value 原结构:** ").append(oldRendered).append("(已删除写入)\n\n");
+ sb.append(" > **value 原结构:** ").append(oldRendered).append("(已删除写入)\n");
} else {
sb.append(" > **value值由:** ").append(oldRendered).append('\n');
- sb.append(" > **变更为:** ").append(newRendered).append("\n\n");
+ sb.append(" > **变更为:** ").append(newRendered).append('\n');
}
+ appendTypeChangeSummary(sb, kc.getFieldDetails());
+ sb.append('\n');
return sb.toString();
}
+ /**
+ * 类型变更单独一行摘要,避免仅靠骨架颜色/占位难以识别。
+ * 例:{@code > **类型变更**: amount BigDecimal → Integer}
+ */
+ private void appendTypeChangeSummary(StringBuilder sb, List details) {
+ if (details == null || details.isEmpty()) {
+ return;
+ }
+ List parts = new ArrayList<>();
+ for (SchemaChange c : details) {
+ if (c == null || c.getChangeType() != ChangeType.TYPE_CHANGED) {
+ continue;
+ }
+ String path = nvl(c.getFieldPath());
+ if (path.isEmpty()) {
+ continue;
+ }
+ String oldType = displayJavaType(c.getOldValue());
+ String newType = displayJavaType(c.getNewValue());
+ StringBuilder part = new StringBuilder();
+ // 字段名用普通文本(避免反引号被企微渲染成色块)
+ part.append(path);
+ if (!oldType.isEmpty() || !newType.isEmpty()) {
+ part.append(" ")
+ .append(oldType.isEmpty() ? "?" : oldType)
+ .append(" → ")
+ .append(newType.isEmpty() ? "?" : newType)
+ .append("");
+ }
+ parts.add(part.toString());
+ }
+ if (parts.isEmpty()) {
+ return;
+ }
+ sb.append(" > **类型变更**: ");
+ for (int i = 0; i < parts.size(); i++) {
+ if (i > 0) {
+ sb.append(";");
+ }
+ sb.append(parts.get(i));
+ }
+ sb.append('\n');
+ }
+
+ /** 将 {@code BigDecimal/NUMBER} 转为展示用 {@code BigDecimal}。 */
+ private static String displayJavaType(String typeLabel) {
+ if (typeLabel == null) {
+ return "";
+ }
+ String s = typeLabel.trim();
+ if (s.isEmpty()) {
+ return "";
+ }
+ int slash = s.indexOf('/');
+ if (slash > 0) {
+ s = s.substring(0, slash).trim();
+ }
+ return s;
+ }
+
/**
* Key 行:展示解析后的模式或未解析时的源码表达式;未解析时追加灰色提示。
* 反引号仅包裹 key 文本,避免与加粗/颜色嵌套冲突。
@@ -160,6 +223,7 @@ public class ReportBuilder {
sb.append("- Key --> `").append(keyText).append('`');
if (unresolved) {
sb.append(" (key 无法解析)");
+// sb.append(" (key 无法解析)");
}
sb.append('\n');
}
diff --git a/src/main/java/com/codechecker/cache/report/SkeletonAnnotator.java b/src/main/java/com/codechecker/cache/report/SkeletonAnnotator.java
index 4ccb811..f2003ca 100644
--- a/src/main/java/com/codechecker/cache/report/SkeletonAnnotator.java
+++ b/src/main/java/com/codechecker/cache/report/SkeletonAnnotator.java
@@ -15,22 +15,24 @@ import java.util.Set;
* - 删除字段 → 旧骨架,橙色 {@code warning}
* - 新增字段 / 新增包装层 → 新骨架,绿色 {@code info}
* - 路径迁移 → 旧路径橙、新路径绿
- * - 类型变更 → 新旧骨架均为橙色 {@code warning}(提高识别度)
+ * - 类型变更(修改类)→ 新旧骨架均为灰色 {@code comment}
*
*/
final class SkeletonAnnotator {
- /** 企微橙:删除 / 类型变更 / 旧侧变更 */
+ /** 企微橙:删除 / 路径迁移旧侧 */
static final String COLOR_REMOVE = "warning";
/** 企微绿:新增 / 新侧路径迁移 */
static final String COLOR_ADD = "info";
+ /** 企微灰:类型变更(修改类) */
+ static final String COLOR_TYPE = "comment";
private static final String FONT_CLOSE = "";
private SkeletonAnnotator() {
}
- /** 旧骨架应橙色标注的路径:删除、路径迁移旧侧、类型变更。 */
+ /** 旧骨架应橙色标注的路径:删除、路径迁移旧侧(不含类型变更)。 */
static Set pathsForOldSkeleton(List details) {
Set paths = new LinkedHashSet<>();
if (details == null) {
@@ -40,8 +42,7 @@ final class SkeletonAnnotator {
if (c == null || c.getChangeType() == null) {
continue;
}
- if (c.getChangeType() == ChangeType.FIELD_REMOVED
- || c.getChangeType() == ChangeType.TYPE_CHANGED) {
+ if (c.getChangeType() == ChangeType.FIELD_REMOVED) {
addIfPresent(paths, c.getFieldPath());
} else if (c.getChangeType() == ChangeType.FIELD_PATH_MOVED) {
addIfPresent(paths, c.getOldValue());
@@ -73,7 +74,7 @@ final class SkeletonAnnotator {
return paths;
}
- /** 新骨架应橙色标注的路径:类型变更(与旧侧同色,便于识别)。 */
+ /** 类型变更路径:新旧骨架均用灰色标注。 */
static Set pathsForTypeChanged(List details) {
Set paths = new LinkedHashSet<>();
if (details == null) {
@@ -88,30 +89,35 @@ final class SkeletonAnnotator {
}
/**
- * 标注旧骨架改动字段(删除 / 类型变更 → 橙色)。
+ * 标注旧骨架:删除等橙色;类型变更灰色。
*/
- static String annotateOldForWecom(String json, Set highlightPaths) {
- return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
+ static String annotateOldForWecom(String json, Set orangePaths) {
+ return annotateOldForWecom(json, orangePaths, null);
+ }
+
+ static String annotateOldForWecom(String json, Set orangePaths, Set grayPaths) {
+ String result = annotateForWecom(json, grayPaths, COLOR_TYPE);
+ return annotateForWecom(result, orangePaths, COLOR_REMOVE);
}
/**
- * 标注新骨架:类型变更先着橙色,其余新增着绿色。
+ * 标注新骨架:类型变更灰色,新增绿色。
*/
static String annotateNewForWecom(String json, Set greenPaths) {
return annotateNewForWecom(json, greenPaths, null);
}
/**
- * 标注新骨架改动字段(类型变更 → 橙色;新增 / 迁移新侧 → 绿色)。
+ * 标注新骨架改动字段(类型变更 → 灰色;新增 / 迁移新侧 → 绿色)。
*/
- static String annotateNewForWecom(String json, Set greenPaths, Set orangePaths) {
- String result = annotateForWecom(json, orangePaths, COLOR_REMOVE);
+ static String annotateNewForWecom(String json, Set greenPaths, Set grayTypePaths) {
+ String result = annotateForWecom(json, grayTypePaths, COLOR_TYPE);
return annotateForWecom(result, greenPaths, COLOR_ADD);
}
/**
* 仅标注改动字段:其余正文保持普通文本。
- * 改动片段格式:{@code "field":value}
+ * 改动片段格式:{@code "field":value}
*/
static String annotateForWecom(String json, Set highlightPaths) {
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
diff --git a/src/test/java/com/codechecker/cache/report/ReportBuilderTest.java b/src/test/java/com/codechecker/cache/report/ReportBuilderTest.java
index 35b8a1c..107c4ca 100644
--- a/src/test/java/com/codechecker/cache/report/ReportBuilderTest.java
+++ b/src/test/java/com/codechecker/cache/report/ReportBuilderTest.java
@@ -112,7 +112,7 @@ class ReportBuilderTest {
}
@Test
- void typeChangedHighlightedOrangeInBothSkeletons() {
+ void typeChangedHighlightedGrayInBothSkeletons() {
SchemaChange typeChanged = new SchemaChange(ChangeType.TYPE_CHANGED);
typeChanged.setFieldPath("amount");
typeChanged.setOldValue("BigDecimal/NUMBER");
@@ -125,20 +125,22 @@ class ReportBuilderTest {
Set oldPaths = SkeletonAnnotator.pathsForOldSkeleton(details);
Set newGreen = SkeletonAnnotator.pathsForNewSkeleton(details);
- Set newOrange = SkeletonAnnotator.pathsForTypeChanged(details);
+ Set typeGray = SkeletonAnnotator.pathsForTypeChanged(details);
- assertEquals(Collections.singleton("amount"), oldPaths);
+ assertTrue(oldPaths.isEmpty());
assertTrue(newGreen.isEmpty());
- assertEquals(Collections.singleton("amount"), newOrange);
+ assertEquals(Collections.singleton("amount"), typeGray);
- String oldMd = SkeletonAnnotator.annotateOldForWecom(oldJson, oldPaths);
- String newMd = SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, newOrange);
+ String oldMd = SkeletonAnnotator.annotateOldForWecom(oldJson, oldPaths, typeGray);
+ String newMd = SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, typeGray);
- assertTrue(oldMd.contains("\"amount\":0"));
- assertFalse(oldMd.contains("\"name\":\"\""));
- assertTrue(newMd.contains("\"amount\":\"\""));
+ assertTrue(oldMd.contains("\"amount\":0"));
+ assertFalse(oldMd.contains("\"amount\""));
+ assertFalse(oldMd.contains("\"name\":\"\""));
+ assertTrue(newMd.contains("\"amount\":\"\""));
assertFalse(newMd.contains("\"amount\""));
- assertFalse(newMd.contains("\"name\":\"\""));
+ assertFalse(newMd.contains("\"amount\""));
+ assertFalse(newMd.contains("\"name\":\"\""));
CheckReport report = baseReport();
KeyStructureChange key = new KeyStructureChange();
@@ -157,9 +159,12 @@ class ReportBuilderTest {
assertTrue(newSection > oldSection);
String oldPart = md.substring(oldSection, newSection);
String newPart = md.substring(newSection);
- assertTrue(oldPart.contains("\"amount\":0"));
- assertTrue(newPart.contains("\"amount\":\"\""));
+ assertTrue(oldPart.contains("\"amount\":0"));
+ assertTrue(newPart.contains("\"amount\":\"\""));
assertFalse(newPart.contains("\"amount\""));
+ assertFalse(oldPart.contains("\"amount\""));
+ assertTrue(md.contains("> **类型变更**: amount BigDecimal → String"),
+ "应包含类型变更摘要行,实际 markdown:\n" + md);
}
@Test