diff --git a/src/main/java/com/codechecker/cache/analyze/FileScanner.java b/src/main/java/com/codechecker/cache/analyze/FileScanner.java index ba18b52..bff9c77 100644 --- a/src/main/java/com/codechecker/cache/analyze/FileScanner.java +++ b/src/main/java/com/codechecker/cache/analyze/FileScanner.java @@ -12,6 +12,7 @@ import java.util.stream.Stream; /** * 扫描工作树中受包含/排除模块限制的 {@code src/main/java} 下的所有 Java 源文件。 + *

返回路径 → 源码内容映射,供 {@link SchemaCheckAnalyzer} 构建新旧 {@code SourceIndex}。

*/ public class FileScanner { diff --git a/src/main/java/com/codechecker/cache/analyze/GlobMatcher.java b/src/main/java/com/codechecker/cache/analyze/GlobMatcher.java index 4001e47..1425316 100644 --- a/src/main/java/com/codechecker/cache/analyze/GlobMatcher.java +++ b/src/main/java/com/codechecker/cache/analyze/GlobMatcher.java @@ -10,6 +10,11 @@ public final class GlobMatcher { private GlobMatcher() { } + /** + * @param glob glob 模式(支持 {@code *} / {@code **}) + * @param input 待匹配字符串 + * @return 是否整串匹配 + */ public static boolean matches(String glob, String input) { if (glob == null || input == null) { return false; diff --git a/src/main/java/com/codechecker/cache/analyze/SchemaCheckAnalyzer.java b/src/main/java/com/codechecker/cache/analyze/SchemaCheckAnalyzer.java index 6f54356..34a5475 100644 --- a/src/main/java/com/codechecker/cache/analyze/SchemaCheckAnalyzer.java +++ b/src/main/java/com/codechecker/cache/analyze/SchemaCheckAnalyzer.java @@ -47,6 +47,14 @@ public class SchemaCheckAnalyzer { this.repoRoot = repoRoot; } + /** + * 对比两个提交之间的序列化结构变更。 + * + * @param oldSha 基准提交 + * @param newSha 当前提交(通常与工作树一致) + * @return 检测报告(含字段明细与按 Key/Topic 聚合结果) + * @throws GitException git 命令失败时 + */ public CheckReport analyze(String oldSha, String newSha) throws GitException { GitDiffScanner scanner = new GitDiffScanner(repoRoot); diff --git a/src/main/java/com/codechecker/cache/cli/SerializationSchemaCheckerMain.java b/src/main/java/com/codechecker/cache/cli/SerializationSchemaCheckerMain.java index 5f9c384..ddb8591 100644 --- a/src/main/java/com/codechecker/cache/cli/SerializationSchemaCheckerMain.java +++ b/src/main/java/com/codechecker/cache/cli/SerializationSchemaCheckerMain.java @@ -15,7 +15,9 @@ import java.util.List; import java.util.concurrent.Callable; /** - * 命令行入口。退出码:0 通过 / 1 阻断 / 2 执行错误。 + * 序列化结构检测工具的命令行入口(picocli)。 + *

加载配置 → 对比 {@code old-sha}/{@code new-sha} → 控制台输出报告 → 可选企微通知。

+ *

退出码:{@code 0} 通过或跳过;{@code 1} block 模式检测到变更;{@code 2} 执行错误。

*/ @Command(name = "serialization-schema-checker", mixinStandardHelpOptions = true, diff --git a/src/main/java/com/codechecker/cache/config/CheckerConfig.java b/src/main/java/com/codechecker/cache/config/CheckerConfig.java index 210dd37..315ec92 100644 --- a/src/main/java/com/codechecker/cache/config/CheckerConfig.java +++ b/src/main/java/com/codechecker/cache/config/CheckerConfig.java @@ -36,6 +36,7 @@ public class CheckerConfig { private List excludeModules = new ArrayList<>(); + /** 企微通知相关配置。 */ public static class Notify { private boolean enabled = true; /** 企微机器人 Webhook 完整 URL */ @@ -76,6 +77,7 @@ public class CheckerConfig { } } + /** 忽略规则:key / 文件路径 / 写入方法 / MQ destination。 */ public static class Ignore { private List keyPatterns = new ArrayList<>(); private List filePatterns = new ArrayList<>(); @@ -116,6 +118,7 @@ public class CheckerConfig { } } + /** 检测模式与推断参数(Redis W*、MQ MQ*、读侧补强开关等)。 */ public static class Detection { private List patterns = new ArrayList<>(); /** MQ 投递检测模式:MQ01~MQ05、MQ-K01~MQ-K04 */ @@ -176,6 +179,9 @@ public class CheckerConfig { } } + /** + * 人工补充映射:在自动推断不准时指定写入方法对应的 key 模式与 value 类型。 + */ public static class ManualMapping { private String id; private String writerMethod; @@ -224,6 +230,9 @@ public class CheckerConfig { } } + /** + * 抑制规则:对已知误报按 key 模式和/或变更类型跳过告警。 + */ public static class Suppression { private String id; private String writerMethod; diff --git a/src/main/java/com/codechecker/cache/config/ConfigLoader.java b/src/main/java/com/codechecker/cache/config/ConfigLoader.java index 5f60bea..7f02552 100644 --- a/src/main/java/com/codechecker/cache/config/ConfigLoader.java +++ b/src/main/java/com/codechecker/cache/config/ConfigLoader.java @@ -22,6 +22,12 @@ public final class ConfigLoader { private ConfigLoader() { } + /** + * 加载并合并配置。 + * + * @param businessConfigPath 业务仓 YAML;为 {@code null} 或不存在时仅使用 jar 内默认配置 + * @return 绑定后的运行配置 + */ public static CheckerConfig load(Path businessConfigPath) { Map merged = loadDefault(); if (businessConfigPath != null && Files.exists(businessConfigPath)) { diff --git a/src/main/java/com/codechecker/cache/detector/BareValueTypes.java b/src/main/java/com/codechecker/cache/detector/BareValueTypes.java index 61c4e0a..a729bbb 100644 --- a/src/main/java/com/codechecker/cache/detector/BareValueTypes.java +++ b/src/main/java/com/codechecker/cache/detector/BareValueTypes.java @@ -9,7 +9,8 @@ import java.util.HashSet; import java.util.Set; /** - * 无业务 JSON 结构的标量 / 裸字节类型(Redis / MQ 共用)。这些类型非序列化,直接跳过。 + * 无业务 JSON 结构的标量 / 裸字节类型判定(Redis / MQ 检测共用)。 + *

命中则不应建立写入点,避免对 String/数字等无结构缓存误报。

*/ final class BareValueTypes { @@ -21,10 +22,21 @@ final class BareValueTypes { private BareValueTypes() { } + /** + * 判断一个 Java 类型是否属于「无业务结构」的标量/裸类型集合。 + * + * @param simple simple + * @return boolean + */ static boolean isBareSimpleName(String simple) { return simple != null && BARE.contains(simple); } + /** + * 从 AST Type 取出简单名后调用 + * @param type type + * @return boolean + */ static boolean isBareType(Type type) { if (type == null) { return false; @@ -44,6 +56,11 @@ final class BareValueTypes { return "byte[]".equals(asString) || isBareSimpleName(asString); } + /** + * 从 FQN 截取最后一段再调用 + * @param fqn fqn + * @return boolean + */ static boolean isBareFqn(String fqn) { if (fqn == null || fqn.isEmpty()) { return false; diff --git a/src/main/java/com/codechecker/cache/detector/CacheReadHintDetector.java b/src/main/java/com/codechecker/cache/detector/CacheReadHintDetector.java index ec2b009..118597b 100644 --- a/src/main/java/com/codechecker/cache/detector/CacheReadHintDetector.java +++ b/src/main/java/com/codechecker/cache/detector/CacheReadHintDetector.java @@ -37,8 +37,6 @@ public class CacheReadHintDetector { "parseObject", "parse", "getJsonToBean", "toJavaObject", "readValue")); private static final Set ARRAY_PARSE = new HashSet<>(Arrays.asList( "parseArray", "getJsonToList", "parseArrayObject")); - private static final Set REDIS_GET = new HashSet<>(Arrays.asList( - "get", "getString", "opsForValue")); private final SourceIndex index; private final RedisKeyResolver keyResolver; @@ -48,6 +46,13 @@ public class CacheReadHintDetector { this.keyResolver = new RedisKeyResolver(index); } + /** + * 扫描读侧反序列化调用,提取可用于补强写入点的类型提示。 + * + * @param filePath 仓库内相对路径 + * @param content 源码全文 + * @return 提示列表(不单独告警) + */ public List detect(String filePath, String content) { List result = new ArrayList<>(); if (content == null || content.isEmpty()) { @@ -69,6 +74,13 @@ public class CacheReadHintDetector { return result; } + /** + * 尝试从单次方法调用提取读侧类型提示;非目标 API 或无法解析类型时返回 null。 + *

+ * 识别 {@code parseObject/parseArray/...} 且第 2 参为 {@code Xxx.class} 的调用, + * 解析出业务类型 FQN;若第 1 参能追溯到 Redis get,则附带 key 并将置信度提至 0.85, + * 否则仅保留类型提示(置信度 0.7)。结果供写入点类型补强,不单独告警。 + */ private CacheReadHint tryParseHint(MethodCallExpr mce, String filePath) { String name = mce.getNameAsString(); boolean array = ARRAY_PARSE.contains(name); @@ -98,6 +110,7 @@ public class CacheReadHintDetector { : enclosing.getFullyQualifiedName().orElse(enclosing.getNameAsString()); SourceIndex.IndexedType context = index.get(enclosingFqn); + // 优先带包前缀解析(如 com.foo.Bar.class),失败再退回简单类名 + import/同包 String fqn = index.resolveFqn(((ClassOrInterfaceType) type).getNameWithScope(), context); if (fqn == null) { fqn = index.resolveFqn(((ClassOrInterfaceType) type).getNameAsString(), context); @@ -116,12 +129,12 @@ public class CacheReadHintDetector { hint.setRootArray(array || "parseArray".equals(name) || "getJsonToList".equals(name)); hint.setConfidence(0.7); + // 第 1 参:原始 JSON 字符串;能关联到 redis.get(key) 时补 key,便于与写入点按 key 匹配 Expression rawExpr = mce.getArgument(0); Optional getRef = findRedisGetForVar(rawExpr, mce); if (getRef.isPresent()) { hint.setKeyExpression(getRef.get().keyExpr.toString()); - hint.setResolvedKeyPattern(keyResolver.resolve( - getRef.get().keyExpr, enclosing, context)); + hint.setResolvedKeyPattern(keyResolver.resolve(getRef.get().keyExpr, enclosing, context)); hint.setConfidence(0.85); } return hint; @@ -155,27 +168,30 @@ public class CacheReadHintDetector { return Optional.empty(); } + /** + * 判断调用是否为 Redis 读 key,是则取出第 1 参作为 key 表达式。 + *

+ * 覆盖常见形态:{@code redisUtil.get/getString(key)}、 + * {@code redisTemplate.opsForValue().get(key)}、{@code boundValueOps.get(key)}。 + * {@code getString} 即使接收者名不含 redis 也接受(RedisUtil 习惯命名); + * 普通 {@code get} 依赖 scope 命中 redis / opsForValue / boundValueOps 启发式。 + * 无法识别时返回 empty,不抛错。 + */ private Optional extractGetKey(MethodCallExpr call) { String name = call.getNameAsString(); + // 接收者字符串小写后做子串匹配,避免绑定具体类型 String scope = call.getScope().map(Expression::toString).orElse("").toLowerCase(Locale.ROOT); - boolean redisScope = scope.contains("redis") || scope.contains("opsforvalue") - || scope.contains("boundvalueops"); + boolean redisScope = scope.contains("redis") || scope.contains("opsforvalue") || scope.contains("boundvalueops"); if ("get".equals(name) || "getString".equals(name)) { - if (!redisScope && !REDIS_GET.contains(name)) { - // getString 也常见于 RedisUtil - if (!"getString".equals(name)) { - return Optional.empty(); - } + // 非 Redis scope:仅放行 getString(RedisUtil 习惯);裸 get(如 map.get)丢弃 + if (!redisScope && !"getString".equals(name)) { + return Optional.empty(); } if (call.getArguments().isEmpty()) { return Optional.empty(); } return Optional.of(new RedisGetRef(call.getArgument(0))); } - // redisTemplate.opsForValue().get(key) - if ("get".equals(name) && scope.contains("opsforvalue") && !call.getArguments().isEmpty()) { - return Optional.of(new RedisGetRef(call.getArgument(0))); - } return Optional.empty(); } diff --git a/src/main/java/com/codechecker/cache/detector/MqReadHintDetector.java b/src/main/java/com/codechecker/cache/detector/MqReadHintDetector.java index 086ae4b..a3fe26e 100644 --- a/src/main/java/com/codechecker/cache/detector/MqReadHintDetector.java +++ b/src/main/java/com/codechecker/cache/detector/MqReadHintDetector.java @@ -51,6 +51,13 @@ public class MqReadHintDetector { this.keyResolver = new RedisKeyResolver(index); } + /** + * 扫描 MQ 消费侧(Listener / parse),提取 destination → 消息体类型提示。 + * + * @param filePath 仓库内相对路径 + * @param content 源码全文 + * @return 提示列表(用于补强生产侧投递点,不单独告警) + */ public List detect(String filePath, String content) { List result = new ArrayList<>(); if (content == null || content.isEmpty()) { diff --git a/src/main/java/com/codechecker/cache/detector/MqWritePointDetector.java b/src/main/java/com/codechecker/cache/detector/MqWritePointDetector.java index 4c7d837..c638275 100644 --- a/src/main/java/com/codechecker/cache/detector/MqWritePointDetector.java +++ b/src/main/java/com/codechecker/cache/detector/MqWritePointDetector.java @@ -67,6 +67,13 @@ public class MqWritePointDetector { this.keyResolver = new RedisKeyResolver(index); } + /** + * 扫描单个 Java 源文件中的 RocketMQ / Kafka 生产侧投递点。 + * + * @param filePath 仓库内相对路径(仅写入报告) + * @param content 源码全文 + * @return 命中的投递点列表(可能为空) + */ public List detect(String filePath, String content) { List result = new ArrayList<>(); if (content == null || content.isEmpty() || enabledPatterns.isEmpty()) { diff --git a/src/main/java/com/codechecker/cache/detector/RedisWritePointDetector.java b/src/main/java/com/codechecker/cache/detector/RedisWritePointDetector.java index 210bf08..23c9c06 100644 --- a/src/main/java/com/codechecker/cache/detector/RedisWritePointDetector.java +++ b/src/main/java/com/codechecker/cache/detector/RedisWritePointDetector.java @@ -61,6 +61,13 @@ public class RedisWritePointDetector { this.keyResolver = new RedisKeyResolver(index); } + /** + * 扫描单个 Java 源文件中的 Redis 写入点。 + * + * @param filePath 仓库内相对路径(仅写入报告) + * @param content 源码全文 + * @return 命中的写入点列表(可能为空) + */ public List detect(String filePath, String content) { List result = new ArrayList<>(); if (content == null || content.isEmpty()) { diff --git a/src/main/java/com/codechecker/cache/detector/WritePoint.java b/src/main/java/com/codechecker/cache/detector/WritePoint.java index 4dc1419..fcf8d26 100644 --- a/src/main/java/com/codechecker/cache/detector/WritePoint.java +++ b/src/main/java/com/codechecker/cache/detector/WritePoint.java @@ -36,6 +36,7 @@ public class WritePoint { return keyExpression == null ? "" : keyExpression.replaceAll("\\s+", ""); } + /** @return 是否为 MQ 通道(RocketMQ / Kafka) */ public boolean isMq() { return CHANNEL_ROCKETMQ.equals(channel) || CHANNEL_KAFKA.equals(channel); } @@ -136,6 +137,9 @@ public class WritePoint { this.channel = channel == null || channel.isEmpty() ? CHANNEL_REDIS : channel; } + /** + * @return 展示用写入位置,形如 {@code SimpleClass#method:line} + */ public String location() { String simpleClass = enclosingClass; if (simpleClass != null && simpleClass.contains(".")) { diff --git a/src/main/java/com/codechecker/cache/diff/ChangeType.java b/src/main/java/com/codechecker/cache/diff/ChangeType.java index 116a890..8d47c44 100644 --- a/src/main/java/com/codechecker/cache/diff/ChangeType.java +++ b/src/main/java/com/codechecker/cache/diff/ChangeType.java @@ -2,6 +2,8 @@ package com.codechecker.cache.diff; /** * 结构变更类型及其默认严重级别。 + *

用于 Diff 结果分类与报告展示标签;实际严重级别可被 {@code severity_overrides} 覆盖。

+ * @author DD */ public enum ChangeType { FIELD_REMOVED(Severity.P0, "字段删除"), diff --git a/src/main/java/com/codechecker/cache/diff/SchemaChange.java b/src/main/java/com/codechecker/cache/diff/SchemaChange.java index 8528115..553ec46 100644 --- a/src/main/java/com/codechecker/cache/diff/SchemaChange.java +++ b/src/main/java/com/codechecker/cache/diff/SchemaChange.java @@ -1,7 +1,8 @@ package com.codechecker.cache.diff; /** - * 一条结构变更记录。 + * 一条结构变更记录:关联 key/destination、写入位置、字段路径与前后取值说明。 + *

由 {@link SchemaDiffer} 或分析器在「新增/删除写入点」时生成,再进入报告明细。

*/ public class SchemaChange { diff --git a/src/main/java/com/codechecker/cache/diff/Severity.java b/src/main/java/com/codechecker/cache/diff/Severity.java index 84e32b3..60bbd66 100644 --- a/src/main/java/com/codechecker/cache/diff/Severity.java +++ b/src/main/java/com/codechecker/cache/diff/Severity.java @@ -1,10 +1,18 @@ package com.codechecker.cache.diff; /** - * 变更严重级别。 + * 变更严重级别(默认由 {@link ChangeType} 给出,可被配置覆盖)。 + *
    + *
  • {@link #P0} — 高危:字段删除、类型变更、包装层/路径迁移等
  • + *
  • {@link #P1} — 中危:新增字段、删除写入点等
  • + *
  • {@link #P2} — 低危/提示:新增写入点、低置信度变更等
  • + *
*/ public enum Severity { + /** 高危结构破坏 */ P0, + /** 中危兼容性风险 */ P1, + /** 低危或需人工确认 */ P2 } diff --git a/src/main/java/com/codechecker/cache/git/GitException.java b/src/main/java/com/codechecker/cache/git/GitException.java index a0540e4..43eb3f1 100644 --- a/src/main/java/com/codechecker/cache/git/GitException.java +++ b/src/main/java/com/codechecker/cache/git/GitException.java @@ -1,14 +1,16 @@ package com.codechecker.cache.git; /** - * Git 操作异常。 + * Git 命令执行失败时抛出的受检异常(如 {@code git diff}/{@code git show} 非零退出)。 */ public class GitException extends Exception { + /** @param message 失败说明(通常含命令与 stderr 摘要) */ public GitException(String message) { super(message); } + /** @param message 失败说明 @param cause 底层原因 */ public GitException(String message, Throwable cause) { super(message, cause); } diff --git a/src/main/java/com/codechecker/cache/key/RedisKeyResolver.java b/src/main/java/com/codechecker/cache/key/RedisKeyResolver.java index 8052dcc..86bb80c 100644 --- a/src/main/java/com/codechecker/cache/key/RedisKeyResolver.java +++ b/src/main/java/com/codechecker/cache/key/RedisKeyResolver.java @@ -32,6 +32,14 @@ public class RedisKeyResolver { this.index = index; } + /** + * 将 key / destination 表达式推断为静态模式字符串。 + * + * @param keyExpr AST 表达式(字面量、常量拼接、format 等) + * @param enclosingClass 所在类(用于查常量/方法) + * @param context 类型索引上下文,可为 {@code null} + * @return 规范化模式;动态段为 {@code *},无法推断时多为 {@code *} 或 {@code unknown-key} 上游处理 + */ public String resolve(Expression keyExpr, ClassOrInterfaceDeclaration enclosingClass, SourceIndex.IndexedType context) { String raw = resolveExpr(keyExpr, enclosingClass, context, 0); diff --git a/src/main/java/com/codechecker/cache/notify/WeComNotifier.java b/src/main/java/com/codechecker/cache/notify/WeComNotifier.java index e18224c..5a36a5d 100644 --- a/src/main/java/com/codechecker/cache/notify/WeComNotifier.java +++ b/src/main/java/com/codechecker/cache/notify/WeComNotifier.java @@ -13,7 +13,8 @@ import java.util.List; import java.util.Map; /** - * 企业微信群机器人通知(markdown 消息)。 + * 企业微信群机器人通知:发送 markdown 消息(单条或多条按序发送)。 + *

Webhook 为空时跳过;HTTP/errcode 失败时打 stderr 日志并返回 false。

*/ public class WeComNotifier { diff --git a/src/main/java/com/codechecker/cache/report/CheckReport.java b/src/main/java/com/codechecker/cache/report/CheckReport.java index f3b9ea1..250ddec 100644 --- a/src/main/java/com/codechecker/cache/report/CheckReport.java +++ b/src/main/java/com/codechecker/cache/report/CheckReport.java @@ -7,7 +7,8 @@ import java.util.ArrayList; import java.util.List; /** - * 一次检测的完整结果。 + * 一次检测的完整结果:元信息、字段级 {@link SchemaChange} 列表、按 Key/Topic 聚合的 + * {@link KeyStructureChange},以及是否阻断流水线与建议退出码。 */ public class CheckReport { @@ -24,10 +25,12 @@ public class CheckReport { private boolean blocked; private int exitCode; + /** @return 是否存在字段级或 Key 级结构变更 */ public boolean hasChanges() { return !changes.isEmpty() || !keyChanges.isEmpty(); } + /** @return 字段明细中指定严重级别的条数 */ public long count(Severity severity) { return changes.stream().filter(c -> c.getSeverity() == severity).count(); } diff --git a/src/main/java/com/codechecker/cache/report/KeyStructureChange.java b/src/main/java/com/codechecker/cache/report/KeyStructureChange.java index e98006e..b08b7f4 100644 --- a/src/main/java/com/codechecker/cache/report/KeyStructureChange.java +++ b/src/main/java/com/codechecker/cache/report/KeyStructureChange.java @@ -75,10 +75,12 @@ public class KeyStructureChange { this.channel = channel == null || channel.isEmpty() ? "REDIS" : channel; } + /** @return 是否为 MQ 通道 */ public boolean isMq() { return "ROCKETMQ".equals(channel) || "KAFKA".equals(channel); } + /** @return 企微展示用通道名(RocketMQ / Kafka / Redis) */ public String channelDisplay() { if ("ROCKETMQ".equals(channel)) { return "RocketMQ"; @@ -117,6 +119,11 @@ public class KeyStructureChange { return fieldDetails; } + /** + * 用更严重的级别抬升当前聚合严重度(ordinal 更小者优先)。 + * + * @param candidate 候选级别;为 null 时忽略 + */ public void raiseSeverity(Severity candidate) { if (candidate == null) { return; diff --git a/src/main/java/com/codechecker/cache/schema/JsonType.java b/src/main/java/com/codechecker/cache/schema/JsonType.java index ac821b5..2be4e5f 100644 --- a/src/main/java/com/codechecker/cache/schema/JsonType.java +++ b/src/main/java/com/codechecker/cache/schema/JsonType.java @@ -1,14 +1,21 @@ package com.codechecker.cache.schema; /** - * 序列化后 JSON 值的粗粒度类型。 + * 序列化后 JSON 值的粗粒度类型,用于 Schema 展开与骨架占位符选择。 */ public enum JsonType { + /** JSON 对象 */ OBJECT, + /** JSON 数组 */ ARRAY, + /** Map 结构 */ MAP, + /** 字符串 */ STRING, + /** 数值 */ NUMBER, + /** 布尔 */ BOOLEAN, + /** 无法判定 */ UNKNOWN } diff --git a/src/main/java/com/codechecker/cache/schema/SkeletonJsonRenderer.java b/src/main/java/com/codechecker/cache/schema/SkeletonJsonRenderer.java index 17c0758..cc32d2c 100644 --- a/src/main/java/com/codechecker/cache/schema/SkeletonJsonRenderer.java +++ b/src/main/java/com/codechecker/cache/schema/SkeletonJsonRenderer.java @@ -16,6 +16,11 @@ public class SkeletonJsonRenderer { /** 单侧骨架默认最大长度(企微 markdown 总长约 4096)。 */ public static final int DEFAULT_MAX_LEN = 1500; + /** + * 渲染完整骨架 JSON,不做长度截断。 + * + * @param schema 扁平 Schema;空或 null 时返回 {@code "{}"} + */ public String render(TypeSchema schema) { return render(schema, null, Integer.MAX_VALUE); } diff --git a/src/main/java/com/codechecker/cache/schema/TypeSchema.java b/src/main/java/com/codechecker/cache/schema/TypeSchema.java index dddd508..1f63750 100644 --- a/src/main/java/com/codechecker/cache/schema/TypeSchema.java +++ b/src/main/java/com/codechecker/cache/schema/TypeSchema.java @@ -32,10 +32,12 @@ public class TypeSchema { return fields; } + /** 按字段 path 放入(同 path 后者覆盖)。 */ public void add(FieldSchema field) { fields.put(field.getPath(), field); } + /** @return 是否无任何可展开字段(空 Schema / 无法解析类型时为 true) */ public boolean isEmpty() { return fields.isEmpty(); }