feat: md渲染优化
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.codechecker.cache.report;
|
package com.codechecker.cache.report;
|
||||||
|
|
||||||
|
import com.codechecker.cache.diff.ChangeType;
|
||||||
import com.codechecker.cache.diff.SchemaChange;
|
import com.codechecker.cache.diff.SchemaChange;
|
||||||
import com.codechecker.cache.diff.Severity;
|
import com.codechecker.cache.diff.Severity;
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ import java.util.Set;
|
|||||||
/**
|
/**
|
||||||
* 将 {@link CheckReport} 渲染为企微 Markdown / 控制台文本。
|
* 将 {@link CheckReport} 渲染为企微 Markdown / 控制台文本。
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>企微:按 key 展示位置/类型/序列化骨架变更,不含字段明细,不分 P0/P1/P2</li>
|
* <li>企微:按 key 展示位置/类型/序列化骨架变更;类型变更另附摘要行,不分 P0/P1/P2</li>
|
||||||
* <li>未解析 key 展示源码表达式 + 灰色「key 无法解析」提示</li>
|
* <li>未解析 key 展示源码表达式 + 灰色「key 无法解析」提示</li>
|
||||||
* <li>多 key 优先拼成一条;超过企微上限则按 key 拆成多条</li>
|
* <li>多 key 优先拼成一条;超过企微上限则按 key 拆成多条</li>
|
||||||
* <li>CI:先打字段明细,再完整输出企微 Markdown(拆分后的每条)</li>
|
* <li>CI:先打字段明细,再完整输出企微 Markdown(拆分后的每条)</li>
|
||||||
@@ -129,22 +130,84 @@ public class ReportBuilder {
|
|||||||
String newJson = nvl(kc.getNewSkeletonJson());
|
String newJson = nvl(kc.getNewSkeletonJson());
|
||||||
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
|
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
|
||||||
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(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()
|
String oldRendered = oldJson.isEmpty()
|
||||||
? "" : "“" + SkeletonAnnotator.annotateOldForWecom(oldJson, oldHighlight) + "”";
|
? "" : "“" + SkeletonAnnotator.annotateOldForWecom(oldJson, oldHighlight, typeGray) + "”";
|
||||||
String newRendered = newJson.isEmpty()
|
String newRendered = newJson.isEmpty()
|
||||||
? "" : "“" + SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, newOrange) + "”";
|
? "" : "“" + SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, typeGray) + "”";
|
||||||
if (oldJson.isEmpty() && !newJson.isEmpty()) {
|
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()) {
|
} else if (!oldJson.isEmpty() && newJson.isEmpty()) {
|
||||||
sb.append(" > **value 原结构:** ").append(oldRendered).append("(已删除写入)\n\n");
|
sb.append(" > **value 原结构:** ").append(oldRendered).append("(已删除写入)\n");
|
||||||
} else {
|
} else {
|
||||||
sb.append(" > **value值由:** ").append(oldRendered).append('\n');
|
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();
|
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 行:展示解析后的模式或未解析时的源码表达式;未解析时追加灰色提示。
|
||||||
* 反引号仅包裹 key 文本,避免与加粗/颜色嵌套冲突。
|
* 反引号仅包裹 key 文本,避免与加粗/颜色嵌套冲突。
|
||||||
@@ -160,6 +223,7 @@ public class ReportBuilder {
|
|||||||
sb.append("- Key --> `").append(keyText).append('`');
|
sb.append("- Key --> `").append(keyText).append('`');
|
||||||
if (unresolved) {
|
if (unresolved) {
|
||||||
sb.append(" <font color=\"comment\">(key 无法解析)</font>");
|
sb.append(" <font color=\"comment\">(key 无法解析)</font>");
|
||||||
|
// sb.append(" (key 无法解析)");
|
||||||
}
|
}
|
||||||
sb.append('\n');
|
sb.append('\n');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,22 +15,24 @@ import java.util.Set;
|
|||||||
* <li>删除字段 → 旧骨架,橙色 {@code warning}</li>
|
* <li>删除字段 → 旧骨架,橙色 {@code warning}</li>
|
||||||
* <li>新增字段 / 新增包装层 → 新骨架,绿色 {@code info}</li>
|
* <li>新增字段 / 新增包装层 → 新骨架,绿色 {@code info}</li>
|
||||||
* <li>路径迁移 → 旧路径橙、新路径绿</li>
|
* <li>路径迁移 → 旧路径橙、新路径绿</li>
|
||||||
* <li>类型变更 → 新旧骨架均为橙色 {@code warning}(提高识别度)</li>
|
* <li>类型变更(修改类)→ 新旧骨架均为灰色 {@code comment}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
final class SkeletonAnnotator {
|
final class SkeletonAnnotator {
|
||||||
|
|
||||||
/** 企微橙:删除 / 类型变更 / 旧侧变更 */
|
/** 企微橙:删除 / 路径迁移旧侧 */
|
||||||
static final String COLOR_REMOVE = "warning";
|
static final String COLOR_REMOVE = "warning";
|
||||||
/** 企微绿:新增 / 新侧路径迁移 */
|
/** 企微绿:新增 / 新侧路径迁移 */
|
||||||
static final String COLOR_ADD = "info";
|
static final String COLOR_ADD = "info";
|
||||||
|
/** 企微灰:类型变更(修改类) */
|
||||||
|
static final String COLOR_TYPE = "comment";
|
||||||
|
|
||||||
private static final String FONT_CLOSE = "</font>";
|
private static final String FONT_CLOSE = "</font>";
|
||||||
|
|
||||||
private SkeletonAnnotator() {
|
private SkeletonAnnotator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 旧骨架应橙色标注的路径:删除、路径迁移旧侧、类型变更。 */
|
/** 旧骨架应橙色标注的路径:删除、路径迁移旧侧(不含类型变更)。 */
|
||||||
static Set<String> pathsForOldSkeleton(List<SchemaChange> details) {
|
static Set<String> pathsForOldSkeleton(List<SchemaChange> details) {
|
||||||
Set<String> paths = new LinkedHashSet<>();
|
Set<String> paths = new LinkedHashSet<>();
|
||||||
if (details == null) {
|
if (details == null) {
|
||||||
@@ -40,8 +42,7 @@ final class SkeletonAnnotator {
|
|||||||
if (c == null || c.getChangeType() == null) {
|
if (c == null || c.getChangeType() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (c.getChangeType() == ChangeType.FIELD_REMOVED
|
if (c.getChangeType() == ChangeType.FIELD_REMOVED) {
|
||||||
|| c.getChangeType() == ChangeType.TYPE_CHANGED) {
|
|
||||||
addIfPresent(paths, c.getFieldPath());
|
addIfPresent(paths, c.getFieldPath());
|
||||||
} else if (c.getChangeType() == ChangeType.FIELD_PATH_MOVED) {
|
} else if (c.getChangeType() == ChangeType.FIELD_PATH_MOVED) {
|
||||||
addIfPresent(paths, c.getOldValue());
|
addIfPresent(paths, c.getOldValue());
|
||||||
@@ -73,7 +74,7 @@ final class SkeletonAnnotator {
|
|||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新骨架应橙色标注的路径:类型变更(与旧侧同色,便于识别)。 */
|
/** 类型变更路径:新旧骨架均用灰色标注。 */
|
||||||
static Set<String> pathsForTypeChanged(List<SchemaChange> details) {
|
static Set<String> pathsForTypeChanged(List<SchemaChange> details) {
|
||||||
Set<String> paths = new LinkedHashSet<>();
|
Set<String> paths = new LinkedHashSet<>();
|
||||||
if (details == null) {
|
if (details == null) {
|
||||||
@@ -88,30 +89,35 @@ final class SkeletonAnnotator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标注旧骨架改动字段(删除 / 类型变更 → 橙色)。
|
* 标注旧骨架:删除等橙色;类型变更灰色。
|
||||||
*/
|
*/
|
||||||
static String annotateOldForWecom(String json, Set<String> highlightPaths) {
|
static String annotateOldForWecom(String json, Set<String> orangePaths) {
|
||||||
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
|
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) {
|
static String annotateNewForWecom(String json, Set<String> greenPaths) {
|
||||||
return annotateNewForWecom(json, greenPaths, null);
|
return annotateNewForWecom(json, greenPaths, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标注新骨架改动字段(类型变更 → 橙色;新增 / 迁移新侧 → 绿色)。
|
* 标注新骨架改动字段(类型变更 → 灰色;新增 / 迁移新侧 → 绿色)。
|
||||||
*/
|
*/
|
||||||
static String annotateNewForWecom(String json, Set<String> greenPaths, Set<String> orangePaths) {
|
static String annotateNewForWecom(String json, Set<String> greenPaths, Set<String> grayTypePaths) {
|
||||||
String result = annotateForWecom(json, orangePaths, COLOR_REMOVE);
|
String result = annotateForWecom(json, grayTypePaths, COLOR_TYPE);
|
||||||
return annotateForWecom(result, greenPaths, COLOR_ADD);
|
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) {
|
static String annotateForWecom(String json, Set<String> highlightPaths) {
|
||||||
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
|
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class ReportBuilderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void typeChangedHighlightedOrangeInBothSkeletons() {
|
void typeChangedHighlightedGrayInBothSkeletons() {
|
||||||
SchemaChange typeChanged = new SchemaChange(ChangeType.TYPE_CHANGED);
|
SchemaChange typeChanged = new SchemaChange(ChangeType.TYPE_CHANGED);
|
||||||
typeChanged.setFieldPath("amount");
|
typeChanged.setFieldPath("amount");
|
||||||
typeChanged.setOldValue("BigDecimal/NUMBER");
|
typeChanged.setOldValue("BigDecimal/NUMBER");
|
||||||
@@ -125,20 +125,22 @@ class ReportBuilderTest {
|
|||||||
|
|
||||||
Set<String> oldPaths = SkeletonAnnotator.pathsForOldSkeleton(details);
|
Set<String> oldPaths = SkeletonAnnotator.pathsForOldSkeleton(details);
|
||||||
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(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());
|
assertTrue(newGreen.isEmpty());
|
||||||
assertEquals(Collections.singleton("amount"), newOrange);
|
assertEquals(Collections.singleton("amount"), typeGray);
|
||||||
|
|
||||||
String oldMd = SkeletonAnnotator.annotateOldForWecom(oldJson, oldPaths);
|
String oldMd = SkeletonAnnotator.annotateOldForWecom(oldJson, oldPaths, typeGray);
|
||||||
String newMd = SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, newOrange);
|
String newMd = SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, typeGray);
|
||||||
|
|
||||||
assertTrue(oldMd.contains("<font color=\"warning\">\"amount\":0</font>"));
|
assertTrue(oldMd.contains("<font color=\"comment\">\"amount\":0</font>"));
|
||||||
assertFalse(oldMd.contains("<font color=\"warning\">\"name\":\"\"</font>"));
|
assertFalse(oldMd.contains("<font color=\"warning\">\"amount\""));
|
||||||
assertTrue(newMd.contains("<font color=\"warning\">\"amount\":\"\"</font>"));
|
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=\"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();
|
CheckReport report = baseReport();
|
||||||
KeyStructureChange key = new KeyStructureChange();
|
KeyStructureChange key = new KeyStructureChange();
|
||||||
@@ -157,9 +159,12 @@ class ReportBuilderTest {
|
|||||||
assertTrue(newSection > oldSection);
|
assertTrue(newSection > oldSection);
|
||||||
String oldPart = md.substring(oldSection, newSection);
|
String oldPart = md.substring(oldSection, newSection);
|
||||||
String newPart = md.substring(newSection);
|
String newPart = md.substring(newSection);
|
||||||
assertTrue(oldPart.contains("<font color=\"warning\">\"amount\":0</font>"));
|
assertTrue(oldPart.contains("<font color=\"comment\">\"amount\":0</font>"));
|
||||||
assertTrue(newPart.contains("<font color=\"warning\">\"amount\":\"\"</font>"));
|
assertTrue(newPart.contains("<font color=\"comment\">\"amount\":\"\"</font>"));
|
||||||
assertFalse(newPart.contains("<font color=\"info\">\"amount\""));
|
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
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user