feat: md渲染优化
This commit is contained in:
@@ -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 / 控制台文本。
|
||||
* <ul>
|
||||
* <li>企微:按 key 展示位置/类型/序列化骨架变更,不含字段明细,不分 P0/P1/P2</li>
|
||||
* <li>企微:按 key 展示位置/类型/序列化骨架变更;类型变更另附摘要行,不分 P0/P1/P2</li>
|
||||
* <li>未解析 key 展示源码表达式 + 灰色「key 无法解析」提示</li>
|
||||
* <li>多 key 优先拼成一条;超过企微上限则按 key 拆成多条</li>
|
||||
* <li>CI:先打字段明细,再完整输出企微 Markdown(拆分后的每条)</li>
|
||||
@@ -129,22 +130,84 @@ public class ReportBuilder {
|
||||
String newJson = nvl(kc.getNewSkeletonJson());
|
||||
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
|
||||
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
|
||||
Set<String> newOrange = SkeletonAnnotator.pathsForTypeChanged(kc.getFieldDetails());
|
||||
Set<String> 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 <font color="warning">BigDecimal → Integer</font>}
|
||||
*/
|
||||
private void appendTypeChangeSummary(StringBuilder sb, List<SchemaChange> details) {
|
||||
if (details == null || details.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> 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(" <font color=\"warning\">")
|
||||
.append(oldType.isEmpty() ? "?" : oldType)
|
||||
.append(" → ")
|
||||
.append(newType.isEmpty() ? "?" : newType)
|
||||
.append("</font>");
|
||||
}
|
||||
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(" <font color=\"comment\">(key 无法解析)</font>");
|
||||
// sb.append(" (key 无法解析)");
|
||||
}
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
@@ -15,22 +15,24 @@ import java.util.Set;
|
||||
* <li>删除字段 → 旧骨架,橙色 {@code warning}</li>
|
||||
* <li>新增字段 / 新增包装层 → 新骨架,绿色 {@code info}</li>
|
||||
* <li>路径迁移 → 旧路径橙、新路径绿</li>
|
||||
* <li>类型变更 → 新旧骨架均为橙色 {@code warning}(提高识别度)</li>
|
||||
* <li>类型变更(修改类)→ 新旧骨架均为灰色 {@code comment}</li>
|
||||
* </ul>
|
||||
*/
|
||||
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 = "</font>";
|
||||
|
||||
private SkeletonAnnotator() {
|
||||
}
|
||||
|
||||
/** 旧骨架应橙色标注的路径:删除、路径迁移旧侧、类型变更。 */
|
||||
/** 旧骨架应橙色标注的路径:删除、路径迁移旧侧(不含类型变更)。 */
|
||||
static Set<String> pathsForOldSkeleton(List<SchemaChange> details) {
|
||||
Set<String> 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<String> pathsForTypeChanged(List<SchemaChange> details) {
|
||||
Set<String> paths = new LinkedHashSet<>();
|
||||
if (details == null) {
|
||||
@@ -88,30 +89,35 @@ final class SkeletonAnnotator {
|
||||
}
|
||||
|
||||
/**
|
||||
* 标注旧骨架改动字段(删除 / 类型变更 → 橙色)。
|
||||
* 标注旧骨架:删除等橙色;类型变更灰色。
|
||||
*/
|
||||
static String annotateOldForWecom(String json, Set<String> highlightPaths) {
|
||||
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
|
||||
static String annotateOldForWecom(String json, Set<String> orangePaths) {
|
||||
return annotateOldForWecom(json, orangePaths, null);
|
||||
}
|
||||
|
||||
static String annotateOldForWecom(String json, Set<String> orangePaths, Set<String> grayPaths) {
|
||||
String result = annotateForWecom(json, grayPaths, COLOR_TYPE);
|
||||
return annotateForWecom(result, orangePaths, COLOR_REMOVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标注新骨架:类型变更先着橙色,其余新增着绿色。
|
||||
* 标注新骨架:类型变更灰色,新增绿色。
|
||||
*/
|
||||
static String annotateNewForWecom(String json, Set<String> greenPaths) {
|
||||
return annotateNewForWecom(json, greenPaths, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标注新骨架改动字段(类型变更 → 橙色;新增 / 迁移新侧 → 绿色)。
|
||||
* 标注新骨架改动字段(类型变更 → 灰色;新增 / 迁移新侧 → 绿色)。
|
||||
*/
|
||||
static String annotateNewForWecom(String json, Set<String> greenPaths, Set<String> orangePaths) {
|
||||
String result = annotateForWecom(json, orangePaths, COLOR_REMOVE);
|
||||
static String annotateNewForWecom(String json, Set<String> greenPaths, Set<String> grayTypePaths) {
|
||||
String result = annotateForWecom(json, grayTypePaths, COLOR_TYPE);
|
||||
return annotateForWecom(result, greenPaths, COLOR_ADD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅标注改动字段:其余正文保持普通文本。
|
||||
* 改动片段格式:{@code <font color="warning|info">"field":value</font>}
|
||||
* 改动片段格式:{@code <font color="warning|info|comment">"field":value</font>}
|
||||
*/
|
||||
static String annotateForWecom(String json, Set<String> highlightPaths) {
|
||||
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
|
||||
|
||||
@@ -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<String> oldPaths = SkeletonAnnotator.pathsForOldSkeleton(details);
|
||||
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(details);
|
||||
Set<String> newOrange = SkeletonAnnotator.pathsForTypeChanged(details);
|
||||
Set<String> 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("<font color=\"warning\">\"amount\":0</font>"));
|
||||
assertFalse(oldMd.contains("<font color=\"warning\">\"name\":\"\"</font>"));
|
||||
assertTrue(newMd.contains("<font color=\"warning\">\"amount\":\"\"</font>"));
|
||||
assertTrue(oldMd.contains("<font color=\"comment\">\"amount\":0</font>"));
|
||||
assertFalse(oldMd.contains("<font color=\"warning\">\"amount\""));
|
||||
assertFalse(oldMd.contains("<font color=\"comment\">\"name\":\"\"</font>"));
|
||||
assertTrue(newMd.contains("<font color=\"comment\">\"amount\":\"\"</font>"));
|
||||
assertFalse(newMd.contains("<font color=\"info\">\"amount\""));
|
||||
assertFalse(newMd.contains("<font color=\"warning\">\"name\":\"\"</font>"));
|
||||
assertFalse(newMd.contains("<font color=\"warning\">\"amount\""));
|
||||
assertFalse(newMd.contains("<font color=\"comment\">\"name\":\"\"</font>"));
|
||||
|
||||
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("<font color=\"warning\">\"amount\":0</font>"));
|
||||
assertTrue(newPart.contains("<font color=\"warning\">\"amount\":\"\"</font>"));
|
||||
assertTrue(oldPart.contains("<font color=\"comment\">\"amount\":0</font>"));
|
||||
assertTrue(newPart.contains("<font color=\"comment\">\"amount\":\"\"</font>"));
|
||||
assertFalse(newPart.contains("<font color=\"info\">\"amount\""));
|
||||
assertFalse(oldPart.contains("<font color=\"warning\">\"amount\""));
|
||||
assertTrue(md.contains("> **类型变更**: amount <font color=\"warning\">BigDecimal → String</font>"),
|
||||
"应包含类型变更摘要行,实际 markdown:\n" + md);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user