Skip to content

<chore>[devtool]: add zstack-dev-tool static checker#3479

Open
zstack-robot-2 wants to merge 4 commits into5.5.12from
sync/ye.zou/chore/zstack-dev-tool@@2
Open

<chore>[devtool]: add zstack-dev-tool static checker#3479
zstack-robot-2 wants to merge 4 commits into5.5.12from
sync/ye.zou/chore/zstack-dev-tool@@2

Conversation

@zstack-robot-2
Copy link
Collaborator

Summary

  • 新增 zstack-dev-tool 独立模块,基于 JavaParser 做静态源码分析
  • 替代 ./runMavenProfile sdk|apihelper|globalconfigdocmd 的 CI 检查,无需编译,秒级完成
  • 三个检查器:GlobalConfig 文档(hard gate)、SDK Action 同步(hard gate)、ApiHelper 方法覆盖(hard gate)

v2 修复内容 (@@2)

  • SdkChecker/ApiHelperChecker passed() 修复: 之前永远返回 true,现在正确检测 field mismatch 和 missing methods
  • classIndex FQCN: 新增 fqcnIndex 防止同名类覆盖导致继承链解析错误
  • DirectoryStream 资源泄漏: addPluginDirs() 改为 try-with-resources
  • 源码目录自动发现: 不再硬编码模块列表,自动 walk 顶层目录
  • resolveTypeClass 修复: 未知类型不再错误地加 java.lang. 前缀
  • 补充测试: ApiMessageScannerTest 7 个用例覆盖 RestRequest 解析、SDK/ApiHelper checker

验证输出 (dev-tool check all on upstream/5.5.12)

[GlobalConfig] Scanned 88 source dirs, found 90 configs in 1408ms
[GlobalConfig] OK - 90 configs, all have docs

[API] Scanned 88 source dirs, found 896 API messages in 4353ms
[SDK] INFO - 14 API message(s) have no SDK action file (may be excluded by @NoSDK)
  (all Daho module - deprecated)
[SDK] FAIL - 1 action(s) out of sync:
  - AddModelAction: source has fields not in SDK: [shareMode]

[ApiHelper] FAIL - MISSING 14 method(s)
  (all Daho module - deprecated)
  • GlobalConfig: 90 configs, 全部有文档 ✅
  • SDK: 发现 1 个真实 field mismatch (AddModelAction.shareMode) ✅
  • ApiHelper: 14 个缺失全部是 Daho(已废弃模块)✅

测试

  • mvn package: 15/15 tests pass (8 GlobalConfig + 7 ApiMessage/SDK/ApiHelper)

包含

  • scanner/: GlobalConfigScanner + ApiMessageScanner(AST 解析)
  • checker/: GlobalConfigDocChecker + SdkChecker + ApiHelperChecker
  • generator/: GlobalConfigDocGenerator(自动生成缺失的 markdown 文档)
  • model/: GlobalConfigInfo + ApiMessageInfo + ApiParamInfo
  • 15 个单元测试
  • 便捷脚本 dev-tool

Resolves: ZSTAC-0

sync from gitlab !9339

AlanJager and others added 2 commits March 12, 2026 14:04
Resolves: ZSTAC-0

Change-Id: I94735b8ca0146f2ea8deed644f72def232fb24dc
Resolves: ZSTAC-0

- Fix SdkChecker/ApiHelperChecker passed() to actually detect failures
  instead of always returning true
- Fix classIndex in ApiMessageScanner to also build FQCN index,
  preventing silent overwrites when two classes share a simple name
- Fix DirectoryStream resource leak in DevTool.addPluginDirs()
  (use try-with-resources)
- Auto-discover source modules instead of hardcoded list
- Fix resolveTypeClass default branch (return simpleName, not
  java.lang.simpleName for unknown types)
- Add ApiMessageScannerTest with 7 tests covering RestRequest parsing,
  abstract/NoSDK exclusion, param extraction, SdkChecker field
  mismatch detection, and ApiHelperChecker missing method detection

Test: mvn package (15/15 tests pass)

Change-Id: Id494379077c18bb49b2776e74e959afdd6dc1917
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: 03836933-82cf-4dd5-ac80-e474226b4587

📥 Commits

Reviewing files that changed from the base of the PR and between eaebd1d and 2d86089.

⛔ Files ignored due to path filters (2)
  • sdk/src/main/java/org/zstack/sdk/GetDevToolTestStatusAction.java is excluded by !sdk/**
  • sdk/src/main/java/org/zstack/sdk/GetDevToolTestStatusResult.java is excluded by !sdk/**
📒 Files selected for processing (1)
  • testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy

Walkthrough

本PR新增并整合了一个 ZStack 开发工具:命令行入口、扫描器、检查器与生成器,用于扫描源代码、校验全局配置/SDK/ApiHelper,并生成缺失的 SDK、ApiHelper 方法及全局配置文档。

Changes

Cohort / File(s) Summary
构建配置
zstack-dev-tool/.gitignore
加入 dependency-reduced-pom.xml 到忽略列表。
启动脚本
zstack-dev-tool/dev-tool
新增 Bash wrapper:定位脚本/ JAR,缺失时触发 Maven 打包并执行 dev-tool JAR。
CLI 核心
zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java
新增主入口类,解析命令(check/generate/scan)、检测项目根、协调扫描、检查与生成工作流并输出结果/退出码。
扫描器
zstack-dev-tool/src/main/java/org/zstack/devtool/scanner/...
ApiMessageScanner.java, GlobalConfigScanner.java
新增基于 JavaParser 的源码扫描器:提取 API 消息(@RestRequest/@APIParam)、继承字段和全局配置(@GlobalConfigDefinition/@GlobalConfigDef)元数据。
数据模型
zstack-dev-tool/src/main/java/org/zstack/devtool/model/...
ApiMessageInfo.java, ApiParamInfo.java, GlobalConfigInfo.java
新增用于承载 API 消息、参数和全局配置元数据的 POJO,含派生辅助方法(如 action/result/helper 名称与 value range)。
检查器
zstack-dev-tool/src/main/java/org/zstack/devtool/checker/...
GlobalConfigDocChecker.java, ApiHelperChecker.java, SdkChecker.java
新增检查器:验证全局配置文档存在性与一致性、ApiHelper 方法覆盖、SDK action 文件及字段与 API 消息的一致性,均返回聚合结果并具可打印报告。
生成器
zstack-dev-tool/src/main/java/org/zstack/devtool/generator/...
GlobalConfigDocGenerator.java, SdkGenerator.java, ApiHelperGenerator.java
新增生成器:生成全局配置 Markdown、生成 SDK Action/Result 源文件,以及向 ApiHelper.groovy 追加缺失 helper 方法(在文件尾插入)。
测试
zstack-dev-tool/src/test/java/org/zstack/devtool/...
ApiMessageScannerTest.java, GlobalConfigScannerTest.java
添加单元测试:覆盖扫描器提取、检查器与生成器的关键行为及若干边界/负面用例。
示例/辅助
testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy
新增 getDevToolTestStatus 方法到 ApiHelper,用作测试/示例调用。

Sequence Diagram(s)

sequenceDiagram
    participant User as 用户
    participant CLI as DevTool CLI
    participant Scanner as Scanner
    participant Checker as Checker
    participant Generator as Generator

    User->>CLI: 运行 check/generate/scan 命令
    CLI->>Scanner: scan(sourceDirs)
    Scanner-->>CLI: 返回 ApiMessageInfo / GlobalConfigInfo 列表

    alt check
        CLI->>Checker: check(infos, targetPath)
        Checker-->>CLI: CheckResult
        CLI-->>User: 输出检查报告并根据结果退出
    else generate
        CLI->>Generator: generate(infos, outputPath)
        Generator-->>CLI: 生成计数
        CLI-->>User: 输出生成汇总
    else scan
        CLI-->>User: 打印扫描到的条目
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 我在源码林间跳跃探寻,
扫描每个注解与配置的踪影,
检查器数清缺失与不符,生成器补写缺页,
小脚印连成工具链的路,
兔子一跃,开发更顺畅。

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题格式正确,遵循 [scope]: 的格式要求,字数为 52 个字符,在 72 个字符限制内。
Description check ✅ Passed PR 描述充分介绍了变更内容、修复要点、验证输出和测试覆盖情况,与变更集高度相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/ye.zou/chore/zstack-dev-tool@@2
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (9)
zstack-dev-tool/src/main/java/org/zstack/devtool/checker/ApiHelperChecker.java (1)

56-78: IOException 处理后结果状态不一致

当读取文件成功但在处理过程中发生 IOException 时,result.totalMethods 保持为 0,但 missingMethods 列表也为空,导致 passed() 返回 true,这可能掩盖实际问题。

建议在 catch 块中将所有消息标记为缺失,或记录错误状态以便调用者知晓检查未完成。

♻️ 建议的修复
         } catch (IOException e) {
             System.out.println("[ApiHelper] ERROR - Cannot read " + apiHelperFile + ": " + e.getMessage());
+            // Mark all as missing since we couldn't verify
+            for (ApiMessageInfo msg : messages) {
+                result.missingMethods.add(msg.getHelperMethodName() + " (from " + msg.getClassName() + ") - unverified");
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/checker/ApiHelperChecker.java`
around lines 56 - 78, When an IOException occurs while reading ApiHelper.groovy
in ApiHelperChecker, the code currently leaves result.totalMethods at 0 and
result.missingMethods empty causing passed() to be true; update the catch block
to mark the check as failed by setting result.totalMethods to messages.size()
and adding every ApiMessageInfo.getHelperMethodName() (with its
msg.getClassName()) to result.missingMethods, or alternatively set an explicit
error flag on result to indicate the check did not complete so callers of
ApiHelperChecker can detect the failure.
zstack-dev-tool/src/main/java/org/zstack/devtool/model/GlobalConfigInfo.java (1)

69-71: 范围显示可读性可优化

当只设置了 numberGreaterThannumberLessThan 其中一个时,会显示类似 [5, 9223372036854775807] 这样的范围,包含 Long.MAX_VALUELong.MIN_VALUE,可读性较差。

可以考虑使用更友好的表示方式,如 (>5)[5, ∞)

♻️ 可选优化
         if (numberGreaterThan != Long.MIN_VALUE || numberLessThan != Long.MAX_VALUE) {
-            return "[" + numberGreaterThan + ", " + numberLessThan + "]";
+            String lower = numberGreaterThan == Long.MIN_VALUE ? "-∞" : String.valueOf(numberGreaterThan);
+            String upper = numberLessThan == Long.MAX_VALUE ? "∞" : String.valueOf(numberLessThan);
+            return "[" + lower + ", " + upper + "]";
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/model/GlobalConfigInfo.java`
around lines 69 - 71, The current range string in GlobalConfigInfo uses raw
Long.MIN_VALUE/Long.MAX_VALUE when only one bound is set, producing unreadable
outputs like "[5, 9223372036854775807]"; update the formatting logic that builds
the range (the code using numberGreaterThan and numberLessThan) to detect the
sentinel values (Long.MIN_VALUE and Long.MAX_VALUE) and emit human-friendly
forms: if only numberGreaterThan is set output "(>X)" or "[X, ∞)"; if only
numberLessThan is set output "(<Y)" or "(-∞, Y]" (choose consistent
inclusive/exclusive notation used elsewhere), and keep the existing "[low,
high]" when both bounds are set—use numberGreaterThan and numberLessThan checks
rather than printing raw min/max.
zstack-dev-tool/src/main/java/org/zstack/devtool/scanner/ApiMessageScanner.java (1)

181-192: hasApiNoSee 设置逻辑存在冗余

在第 186 行,如果 hasApiNoSeetrue 则执行 continue,所以第 192 行的 param.setNoSee(hasApiNoSee) 永远只会设置为 false。这段代码可以简化。

♻️ 建议简化
             boolean hasApiParam = field.getAnnotationByName("APIParam").isPresent();
-            boolean hasApiNoSee = field.getAnnotationByName("APINoSee").isPresent();
 
             // SDK only includes fields with `@APIParam` (excluding `@APINoSee`)
             if (!hasApiParam) continue;
-            if (hasApiNoSee) continue;
+            if (field.getAnnotationByName("APINoSee").isPresent()) continue;
 
             for (VariableDeclarator var : field.getVariables()) {
                 ApiParamInfo param = new ApiParamInfo();
                 param.setFieldName(var.getNameAsString());
                 param.setFieldType(resolveFieldType(field));
-                param.setNoSee(hasApiNoSee);
+                // noSee is always false here since we continued above if APINoSee present
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/scanner/ApiMessageScanner.java`
around lines 181 - 192, The code computes hasApiNoSee but then continues when
it's true, so param.setNoSee(hasApiNoSee) is always false and redundant; in
ApiMessageScanner remove the needless param.setNoSee(hasApiNoSee) call (or
replace it with an explicit param.setNoSee(false) if you prefer clarity) and
keep the existing continue check for hasApiNoSee, ensuring hasApiNoSee is only
used to skip fields and not to set the ApiParamInfo flag.
zstack-dev-tool/src/test/java/org/zstack/devtool/GlobalConfigScannerTest.java (2)

15-15: 未使用的导入

java.util.Arrays 被导入但未在代码中使用。

🧹 建议移除
-import java.util.Arrays;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@zstack-dev-tool/src/test/java/org/zstack/devtool/GlobalConfigScannerTest.java`
at line 15, The import java.util.Arrays in the GlobalConfigScannerTest class is
unused—remove the unused import statement from GlobalConfigScannerTest (the
import line for java.util.Arrays) to clean up the code and avoid IDE/compiler
warnings; no other changes to class methods or tests are needed.

26-33: 辅助方法在同一测试中多次调用可能失败

createJavaFile() 方法每次调用都尝试创建 src/main/java 目录结构。如果在同一个测试中多次调用,tempDir.newFolder() 会因目录已存在而抛出异常。当前测试用例每个只调用一次,所以没有问题,但如果未来扩展测试可能会遇到问题。

🔧 可选改进:使用 getRoot() 替代 newFolder()
     private File createJavaFile(String filename, String content) throws Exception {
-        File dir = tempDir.newFolder("src", "main", "java");
+        File dir = new File(tempDir.getRoot(), "src/main/java");
+        dir.mkdirs();
         File file = new File(dir, filename);
         try (FileWriter w = new FileWriter(file)) {
             w.write(content);
         }
         return dir;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@zstack-dev-tool/src/test/java/org/zstack/devtool/GlobalConfigScannerTest.java`
around lines 26 - 33, createJavaFile currently calls
tempDir.newFolder("src","main","java") which will fail if called multiple times;
change createJavaFile to use tempDir.getRoot() (or tempDir.getRoot().toPath())
as the base, then ensure the "src/main/java" directories exist by creating them
with mkdirs() or Files.createDirectories before writing the file; update the
method to return the created File (the variable file) and keep the FileWriter
logic in createJavaFile to write content to that file.
zstack-dev-tool/src/main/java/org/zstack/devtool/checker/GlobalConfigDocChecker.java (1)

76-100: 可选优化:contains() 检查可能产生误报

当前使用简单的 content.contains() 进行一致性检查,例如类型 "Long" 可能会错误匹配文档中的 "LongRunning" 等字符串。对于文档一致性检查来说,这种轻微的误报风险是可以接受的,但如果需要更精确的匹配,可以考虑使用正则表达式或检查特定的 Markdown 格式。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/checker/GlobalConfigDocChecker.java`
around lines 76 - 100, The checkConsistency method in GlobalConfigDocChecker
uses naive content.contains(...) calls (e.g., for expectedType, expectedDefault,
and config.getName()) which can yield false positives like matching "Long"
inside "LongRunning"; update checkConsistency to perform stricter matches by
using regex word-boundary or Markdown-aware patterns (e.g., \bTYPE\b or matching
code/heading tokens) when verifying expectedType, expectedDefault and name
presence so only exact tokens or specific markdown constructs are considered;
replace the content.contains(...) checks for expectedType, expectedDefault and
config.getName() with these regex/markdown checks while preserving the same
result.inconsistent messages when a mismatch is detected.
zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java (1)

243-274: 硬编码的排除目录列表可能需要维护

getSourceDirs() 方法硬编码了要排除的目录列表(Line 252-256)。当项目结构变化或新增模块时,可能需要更新此列表。

建议考虑:

  1. 将排除列表提取为常量以便维护
  2. 或者采用包含策略(检测含有 pom.xml 的目录)而非排除策略

这是一个可选的维护性改进,当前实现功能正确。

🔧 可选:提取排除列表为常量
+    private static final Set<String> EXCLUDED_DIRS = Set.of(
+            "premium", "plugin", "sdk", "test", "testlib",
+            "zstack-dev-tool", "build", "doc", "conf", "tools"
+    );
+
     private List<Path> getSourceDirs() {
         List<Path> dirs = new ArrayList<>();
 
         try (DirectoryStream<Path> stream = Files.newDirectoryStream(projectRoot)) {
             for (Path child : stream) {
                 if (!Files.isDirectory(child)) continue;
                 String name = child.getFileName().toString();
-                if (name.startsWith(".") || "premium".equals(name) || "plugin".equals(name)
-                        || "sdk".equals(name) || "test".equals(name) || "testlib".equals(name)
-                        || "zstack-dev-tool".equals(name) || "build".equals(name)
-                        || "doc".equals(name) || "conf".equals(name) || "tools".equals(name)) {
+                if (name.startsWith(".") || EXCLUDED_DIRS.contains(name)) {
                     continue;
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java` around lines
243 - 274, getSourceDirs currently uses a hard-coded exclusion list inline which
is brittle; extract that list to a single static constant (e.g., EXCLUDED_DIRS
as a Set<String>) and replace the inline name.startsWith/equals checks with a
membership check against EXCLUDED_DIRS in getSourceDirs (referencing
getSourceDirs and projectRoot to locate the loop), or switch to an inclusion
strategy by checking for module markers (e.g., child.resolve("pom.xml") or other
canonical files) and only adding child.resolve("src/main/java") when that marker
exists; update addPluginDirs usage unchanged and ensure the new constant is
well-named and documented.
zstack-dev-tool/dev-tool (2)

10-10: 可选:静默模式可能隐藏构建错误

mvn package -DskipTests -q-q(quiet)模式会抑制大部分输出。如果构建失败,用户可能看不到有用的错误信息。

可以考虑使用 -q 仅在成功时生效,或者保留警告级别的输出。

🔧 可选:保留警告输出
-    cd "$SCRIPT_DIR" && mvn package -DskipTests -q
+    cd "$SCRIPT_DIR" && mvn package -DskipTests --batch-mode -Dorg.slf4j.simpleLogger.defaultLogLevel=WARN

或者更简单地移除 -q

-    cd "$SCRIPT_DIR" && mvn package -DskipTests -q
+    cd "$SCRIPT_DIR" && mvn package -DskipTests
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/dev-tool` at line 10, The current build command string `cd
"$SCRIPT_DIR" && mvn package -DskipTests -q` uses Maven's `-q` which can hide
useful error output; change it to either remove `-q` entirely or make it
conditional: run `mvn package -DskipTests -q`, capture the exit code, and if it
fails re-run `mvn package -DskipTests` (without `-q`) so you surface errors;
update the script where that exact command appears to implement one of these two
behaviors.

17-17: 可选:添加 cd 失败检查

如果 cd "$SCRIPT_DIR/.." 失败(虽然不太可能),脚本会在错误的目录下执行 Java 命令。

🔧 可选:添加错误检查
-cd "$SCRIPT_DIR/.." && java -jar "$JAR" "$@"
+cd "$SCRIPT_DIR/.." || { echo "ERROR: Cannot change to project root"; exit 1; }
+java -jar "$JAR" "$@"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/dev-tool` at line 17, Ensure the script checks that changing
directory succeeds before running Java: after the cd "$SCRIPT_DIR/.." step (the
line currently using cd "$SCRIPT_DIR/.." && java -jar "$JAR" "$@"), detect a
failed cd and abort with an error message and non‑zero exit (e.g., emit a clear
error via echo or logger and exit 1) so the subsequent java -jar invocation
never runs in the wrong directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstack-dev-tool/src/main/java/org/zstack/devtool/checker/SdkChecker.java`:
- Around line 57-63: The Files.list(sdkDir) stream is not closed, risking
resource leaks; update the block that sets result.totalSdkFiles to open the
stream in a try-with-resources (or otherwise ensure closure) when calling
Files.list(sdkDir). Specifically, wrap Files.list(sdkDir) in a try (Stream<Path>
stream = Files.list(sdkDir)) { result.totalSdkFiles = (int) stream.filter(p ->
p.getFileName().toString().endsWith("Action.java")).count(); } and preserve the
existing catch(IOException e) behavior that sets result.totalSdkFiles = 0;
reference the variables/methods result.totalSdkFiles, sdkDir, Files.list and the
lambda p -> p.getFileName().toString().endsWith("Action.java") when locating the
code to change.
- Around line 88-95: The Files.walk(...) Stream in SdkChecker (the method that
searches for actionName + ".java") is not closed; wrap the Files.walk(sdkDir)
call in a try-with-resources (e.g., try (Stream<Path> stream =
Files.walk(sdkDir)) { ... }) so the stream is closed automatically, then perform
the filter/findFirst on that stream and return found.orElse(null); also keep the
existing IOException catch to return null.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/generator/GlobalConfigDocGenerator.java`:
- Line 89: In GlobalConfigDocGenerator (the code that builds the Markdown table
row using sb.append("|").append(res).append("\n")), fix the Markdown row by
appending the closing pipe before the newline; update the string building in
that method so each row becomes "|<value>|\n" (i.e., change the append sequence
to add the trailing "|" prior to "\n") to ensure proper Markdown table
rendering.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/model/ApiMessageInfo.java`:
- Around line 43-48: 在 getHelperMethodName() 中对从 getActionName() 返回的 action
做防御性检查:先检查 action 是否为 null 且长度是否至少为 6(因代码期望去掉尾部 "Action"),如果不满足则给出明确的处理(例如抛出
IllegalStateException/IllegalArgumentException 带上具体信息,或返回一个明确的默认值),否则按现有逻辑截取
name = action.substring(0, action.length() - 6) 并小写首字母后返回;确保引用
getActionName()、getHelperMethodName() 的位置都能接受你选择的错误处理方式并在异常消息中包含有助排查的上下文(比如
className 或 action)。

---

Nitpick comments:
In `@zstack-dev-tool/dev-tool`:
- Line 10: The current build command string `cd "$SCRIPT_DIR" && mvn package
-DskipTests -q` uses Maven's `-q` which can hide useful error output; change it
to either remove `-q` entirely or make it conditional: run `mvn package
-DskipTests -q`, capture the exit code, and if it fails re-run `mvn package
-DskipTests` (without `-q`) so you surface errors; update the script where that
exact command appears to implement one of these two behaviors.
- Line 17: Ensure the script checks that changing directory succeeds before
running Java: after the cd "$SCRIPT_DIR/.." step (the line currently using cd
"$SCRIPT_DIR/.." && java -jar "$JAR" "$@"), detect a failed cd and abort with an
error message and non‑zero exit (e.g., emit a clear error via echo or logger and
exit 1) so the subsequent java -jar invocation never runs in the wrong
directory.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/checker/ApiHelperChecker.java`:
- Around line 56-78: When an IOException occurs while reading ApiHelper.groovy
in ApiHelperChecker, the code currently leaves result.totalMethods at 0 and
result.missingMethods empty causing passed() to be true; update the catch block
to mark the check as failed by setting result.totalMethods to messages.size()
and adding every ApiMessageInfo.getHelperMethodName() (with its
msg.getClassName()) to result.missingMethods, or alternatively set an explicit
error flag on result to indicate the check did not complete so callers of
ApiHelperChecker can detect the failure.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/checker/GlobalConfigDocChecker.java`:
- Around line 76-100: The checkConsistency method in GlobalConfigDocChecker uses
naive content.contains(...) calls (e.g., for expectedType, expectedDefault, and
config.getName()) which can yield false positives like matching "Long" inside
"LongRunning"; update checkConsistency to perform stricter matches by using
regex word-boundary or Markdown-aware patterns (e.g., \bTYPE\b or matching
code/heading tokens) when verifying expectedType, expectedDefault and name
presence so only exact tokens or specific markdown constructs are considered;
replace the content.contains(...) checks for expectedType, expectedDefault and
config.getName() with these regex/markdown checks while preserving the same
result.inconsistent messages when a mismatch is detected.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java`:
- Around line 243-274: getSourceDirs currently uses a hard-coded exclusion list
inline which is brittle; extract that list to a single static constant (e.g.,
EXCLUDED_DIRS as a Set<String>) and replace the inline name.startsWith/equals
checks with a membership check against EXCLUDED_DIRS in getSourceDirs
(referencing getSourceDirs and projectRoot to locate the loop), or switch to an
inclusion strategy by checking for module markers (e.g.,
child.resolve("pom.xml") or other canonical files) and only adding
child.resolve("src/main/java") when that marker exists; update addPluginDirs
usage unchanged and ensure the new constant is well-named and documented.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/model/GlobalConfigInfo.java`:
- Around line 69-71: The current range string in GlobalConfigInfo uses raw
Long.MIN_VALUE/Long.MAX_VALUE when only one bound is set, producing unreadable
outputs like "[5, 9223372036854775807]"; update the formatting logic that builds
the range (the code using numberGreaterThan and numberLessThan) to detect the
sentinel values (Long.MIN_VALUE and Long.MAX_VALUE) and emit human-friendly
forms: if only numberGreaterThan is set output "(>X)" or "[X, ∞)"; if only
numberLessThan is set output "(<Y)" or "(-∞, Y]" (choose consistent
inclusive/exclusive notation used elsewhere), and keep the existing "[low,
high]" when both bounds are set—use numberGreaterThan and numberLessThan checks
rather than printing raw min/max.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/scanner/ApiMessageScanner.java`:
- Around line 181-192: The code computes hasApiNoSee but then continues when
it's true, so param.setNoSee(hasApiNoSee) is always false and redundant; in
ApiMessageScanner remove the needless param.setNoSee(hasApiNoSee) call (or
replace it with an explicit param.setNoSee(false) if you prefer clarity) and
keep the existing continue check for hasApiNoSee, ensuring hasApiNoSee is only
used to skip fields and not to set the ApiParamInfo flag.

In
`@zstack-dev-tool/src/test/java/org/zstack/devtool/GlobalConfigScannerTest.java`:
- Line 15: The import java.util.Arrays in the GlobalConfigScannerTest class is
unused—remove the unused import statement from GlobalConfigScannerTest (the
import line for java.util.Arrays) to clean up the code and avoid IDE/compiler
warnings; no other changes to class methods or tests are needed.
- Around line 26-33: createJavaFile currently calls
tempDir.newFolder("src","main","java") which will fail if called multiple times;
change createJavaFile to use tempDir.getRoot() (or tempDir.getRoot().toPath())
as the base, then ensure the "src/main/java" directories exist by creating them
with mkdirs() or Files.createDirectories before writing the file; update the
method to return the created File (the variable file) and keep the FileWriter
logic in createJavaFile to write content to that file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: a4e99274-7e43-47f7-9da7-ff60f514e20a

📥 Commits

Reviewing files that changed from the base of the PR and between 681a301 and 9a25e32.

⛔ Files ignored due to path filters (1)
  • zstack-dev-tool/pom.xml is excluded by !**/*.xml
📒 Files selected for processing (14)
  • zstack-dev-tool/.gitignore
  • zstack-dev-tool/dev-tool
  • zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/checker/ApiHelperChecker.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/checker/GlobalConfigDocChecker.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/checker/SdkChecker.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/generator/GlobalConfigDocGenerator.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/model/ApiMessageInfo.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/model/ApiParamInfo.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/model/GlobalConfigInfo.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/scanner/ApiMessageScanner.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/scanner/GlobalConfigScanner.java
  • zstack-dev-tool/src/test/java/org/zstack/devtool/ApiMessageScannerTest.java
  • zstack-dev-tool/src/test/java/org/zstack/devtool/GlobalConfigScannerTest.java

Comment on lines +57 to +63
try {
result.totalSdkFiles = (int) Files.list(sdkDir)
.filter(p -> p.getFileName().toString().endsWith("Action.java"))
.count();
} catch (IOException e) {
result.totalSdkFiles = 0;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Files.list() 返回的 Stream 需要关闭以避免资源泄漏

Files.list() 返回一个需要显式关闭的 Stream<Path>。当前实现可能导致文件句柄泄漏。

🔧 建议使用 try-with-resources
         // count existing SDK action files
-        try {
-            result.totalSdkFiles = (int) Files.list(sdkDir)
+        try (var stream = Files.list(sdkDir)) {
+            result.totalSdkFiles = (int) stream
                     .filter(p -> p.getFileName().toString().endsWith("Action.java"))
                     .count();
         } catch (IOException e) {
             result.totalSdkFiles = 0;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/checker/SdkChecker.java`
around lines 57 - 63, The Files.list(sdkDir) stream is not closed, risking
resource leaks; update the block that sets result.totalSdkFiles to open the
stream in a try-with-resources (or otherwise ensure closure) when calling
Files.list(sdkDir). Specifically, wrap Files.list(sdkDir) in a try (Stream<Path>
stream = Files.list(sdkDir)) { result.totalSdkFiles = (int) stream.filter(p ->
p.getFileName().toString().endsWith("Action.java")).count(); } and preserve the
existing catch(IOException e) behavior that sets result.totalSdkFiles = 0;
reference the variables/methods result.totalSdkFiles, sdkDir, Files.list and the
lambda p -> p.getFileName().toString().endsWith("Action.java") when locating the
code to change.

Comment on lines +88 to +95
try {
Optional<Path> found = Files.walk(sdkDir)
.filter(p -> p.getFileName().toString().equals(actionName + ".java"))
.findFirst();
return found.orElse(null);
} catch (IOException e) {
return null;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Files.walk() 返回的 Stream 同样需要关闭

Files.list() 类似,Files.walk() 返回的 Stream 也需要关闭以释放文件系统资源。

🔧 建议使用 try-with-resources
         // Check subdirectories (some actions are in sub-packages)
-        try {
-            Optional<Path> found = Files.walk(sdkDir)
+        try (var stream = Files.walk(sdkDir)) {
+            Optional<Path> found = stream
                     .filter(p -> p.getFileName().toString().equals(actionName + ".java"))
                     .findFirst();
             return found.orElse(null);
         } catch (IOException e) {
             return null;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/checker/SdkChecker.java`
around lines 88 - 95, The Files.walk(...) Stream in SdkChecker (the method that
searches for actionName + ".java") is not closed; wrap the Files.walk(sdkDir)
call in a try-with-resources (e.g., try (Stream<Path> stream =
Files.walk(sdkDir)) { ... }) so the stream is closed automatically, then perform
the filter/findFirst on that stream and return found.orElse(null); also keep the
existing IOException catch to return null.

if (resources != null && !resources.isEmpty()) {
sb.append("||\n|---|\n");
for (String res : resources) {
sb.append("|").append(res).append("\n");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Markdown 表格格式可能不完整

Line 89 的表格行缺少闭合的 |,当前输出格式为 |resource\n 而不是 |resource|\n,这可能导致某些 Markdown 解析器无法正确渲染表格。

🔧 建议修复
             for (String res : resources) {
-                sb.append("|").append(res).append("\n");
+                sb.append("| ").append(res).append(" |\n");
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/generator/GlobalConfigDocGenerator.java`
at line 89, In GlobalConfigDocGenerator (the code that builds the Markdown table
row using sb.append("|").append(res).append("\n")), fix the Markdown row by
appending the closing pipe before the newline; update the string building in
that method so each row becomes "|<value>|\n" (i.e., change the append sequence
to add the trailing "|" prior to "\n") to ensure proper Markdown table
rendering.

Comment on lines +43 to +48
public String getHelperMethodName() {
// CreateZoneAction -> createZone
String action = getActionName();
String name = action.substring(0, action.length() - 6); // strip "Action"
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

getHelperMethodName() 中缺少对空/短字符串的保护

如果 classNamenull,或者转换后的 action 名称少于 6 个字符,此方法会抛出 NullPointerExceptionStringIndexOutOfBoundsException

🛡️ 建议添加防御性检查
     public String getHelperMethodName() {
         // CreateZoneAction -> createZone
         String action = getActionName();
+        if (action == null || action.length() <= 6) {
+            return action != null ? action.toLowerCase() : "";
+        }
         String name = action.substring(0, action.length() - 6); // strip "Action"
+        if (name.isEmpty()) {
+            return "";
+        }
         return Character.toLowerCase(name.charAt(0)) + name.substring(1);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/model/ApiMessageInfo.java`
around lines 43 - 48, 在 getHelperMethodName() 中对从 getActionName() 返回的 action
做防御性检查:先检查 action 是否为 null 且长度是否至少为 6(因代码期望去掉尾部 "Action"),如果不满足则给出明确的处理(例如抛出
IllegalStateException/IllegalArgumentException 带上具体信息,或返回一个明确的默认值),否则按现有逻辑截取
name = action.substring(0, action.length() - 6) 并小写首字母后返回;确保引用
getActionName()、getHelperMethodName() 的位置都能接受你选择的错误处理方式并在异常消息中包含有助排查的上下文(比如
className 或 action)。

Resolves: ZSTAC-0

Change-Id: I77ed018901e69b3073e3b6ece77ae4867173e166
@MatheMatrix MatheMatrix force-pushed the sync/ye.zou/chore/zstack-dev-tool@@2 branch from 5142628 to eaebd1d Compare March 17, 2026 07:32
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java`:
- Around line 35-36: The CLI treats any unknown first arg as a valid command and
silently succeeds (e.g., args[0] wrong makes check("sdkk") return true and
generate/scan no-op), so validate the command early: in main() after parsing
command and target, ensure command is one of the allowed values (e.g.,
"check","generate","scan","help") and exit with a non-zero status and an error
log if not; additionally, add defensive validation inside the public methods
check(), generate(), and scan() to reject unknown targets/commands when called
directly so callers cannot cause false positives.
- Around line 121-125: The check code currently calls
resolveGlobalConfigDocDir() which creates the docs directory, causing side
effects; change the API so existence creation is controlled: add a boolean flag
(e.g., createIfMissing) to resolveGlobalConfigDocDir (or to
GlobalConfigDocChecker.check) and make checkGlobalConfig() call it with false
while generateGlobalConfig() calls it with true; update the calls to
GlobalConfigDocChecker.check(configs, docDir) to pass the flag (or resolve the
dir with the flag) and ensure the directory is only created when
createIfMissing==true (apply same change to the other occurrence referenced).

In
`@zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java`:
- Around line 268-280: The generated Result class is currently always empty,
losing APIReply/APIEvent fields so makeResult() cannot deserialize real response
data; update generateResultContent(ApiMessageInfo msg) to emit the actual fields
from the response/reply/event description available on ApiMessageInfo (iterate
its field descriptors, map their types to Java SDK types, and append
corresponding private fields and getters/setters into the class body for
resultName) and only emit an empty class when no fields are present; ensure the
produced class shape matches what makeResult() expects for deserialization and
include any necessary imports or nested inventory type references when writing
the class text.
- Around line 79-82: The default for ApiMessageInfo.parameterName must be
"params" instead of an empty string; update the initialization of parameterName
in SdkGenerator (where boolean needSession and needPoll are computed) to use
msg.getParameterName() != null ? msg.getParameterName() : "params", and make the
analogous change in the generateQueryActionContent() branch that currently falls
back to "" so both action generators produce the correct request wrapper when
parameterName is unspecified.
- Around line 322-331: The code appends raw values from param.getValidValues()
and param.getValidRegexValues() into generated Java string literals, which
breaks compilation and regex semantics when values contain backslashes, quotes,
or control chars; fix by escaping these strings before concatenation (e.g.,
create and call an escapeForJavaStringLiteral(String) helper used when appending
param.getValidValues()[i] and when appending param.getValidRegexValues()),
ensuring backslashes become "\\" , quotes become "\"" and newlines/tabs/carriage
returns/formfeeds are escaped so the generated source is valid Java and regexes
keep their intended backslash escapes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: http://open.zstack.ai:20001/code-reviews/zstack-cloud.yaml (via .coderabbit.yaml)

Review profile: CHILL

Plan: Pro

Run ID: e0ebc413-dd54-4e90-93a8-00be98911ffc

📥 Commits

Reviewing files that changed from the base of the PR and between 99b5cc5 and eaebd1d.

📒 Files selected for processing (3)
  • zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/generator/ApiHelperGenerator.java
  • zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java

Comment on lines +35 to +36
String command = args[0];
String target = args.length > 1 ? args[1] : "all";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

未知 target 现在会被当成成功执行。

check("sdkk") 会直接返回 truegenerate/scan 也会静默什么都不做。命令行参数一旦拼错,CI 和本地都会得到假阳性。

💡 建议修改
+    private static boolean isValidTarget(String target) {
+        return "all".equals(target)
+                || "globalconfig".equals(target)
+                || "sdk".equals(target)
+                || "apihelper".equals(target);
+    }
+
     public static void main(String[] args) {
         if (args.length < 1) {
             printUsage();
             System.exit(1);
         }
 
         String command = args[0];
         String target = args.length > 1 ? args[1] : "all";
+        if (!isValidTarget(target)) {
+            System.err.println("Unknown target: " + target);
+            printUsage();
+            System.exit(1);
+        }

如果这些 public 方法还会被其他调用方直接使用,建议在 check()/generate()/scan() 里也做同样的保护。

Also applies to: 63-110

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java` around lines
35 - 36, The CLI treats any unknown first arg as a valid command and silently
succeeds (e.g., args[0] wrong makes check("sdkk") return true and generate/scan
no-op), so validate the command early: in main() after parsing command and
target, ensure command is one of the allowed values (e.g.,
"check","generate","scan","help") and exit with a non-zero status and an error
log if not; additionally, add defensive validation inside the public methods
check(), generate(), and scan() to reject unknown targets/commands when called
directly so callers cannot cause false positives.

Comment on lines +121 to +125
Path docDir = resolveGlobalConfigDocDir();
GlobalConfigDocChecker checker = new GlobalConfigDocChecker();
GlobalConfigDocChecker.CheckResult result = checker.check(configs, docDir);
result.print();
return result.passed();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

check 路径不要创建文档目录。

checkGlobalConfig() 现在会经由 resolveGlobalConfigDocDir() 走到目录创建逻辑;一次只读校验会修改工作树 / CI 工作区,这很容易把“检查”变成“带副作用的修复”。

💡 建议修改
-    private Path resolveGlobalConfigDocDir() {
+    private Path resolveGlobalConfigDocDir(boolean createIfMissing) {
         Path premiumDoc = projectRoot.resolve("premium/doc/globalconfig");
         if (Files.isDirectory(premiumDoc)) return premiumDoc;
         Path doc = projectRoot.resolve("doc/globalconfig");
         if (Files.isDirectory(doc)) return doc;
-        try {
-            Files.createDirectories(premiumDoc);
-        } catch (IOException e) {
-            throw new RuntimeException("Cannot create " + premiumDoc, e);
+        if (!createIfMissing) {
+            return premiumDoc;
         }
-        return premiumDoc;
+        try {
+            Files.createDirectories(premiumDoc);
+            return premiumDoc;
+        } catch (IOException e) {
+            throw new RuntimeException("Cannot create " + premiumDoc, e);
+        }
     }

然后让 checkGlobalConfig()falsegenerateGlobalConfig()true

Also applies to: 265-275

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/DevTool.java` around lines
121 - 125, The check code currently calls resolveGlobalConfigDocDir() which
creates the docs directory, causing side effects; change the API so existence
creation is controlled: add a boolean flag (e.g., createIfMissing) to
resolveGlobalConfigDocDir (or to GlobalConfigDocChecker.check) and make
checkGlobalConfig() call it with false while generateGlobalConfig() calls it
with true; update the calls to GlobalConfigDocChecker.check(configs, docDir) to
pass the flag (or resolve the dir with the flag) and ensure the directory is
only created when createIfMissing==true (apply same change to the other
occurrence referenced).

Comment on lines +79 to +82
boolean needSession = !msg.isSuppressCredentialCheck();
boolean needPoll = isWriteMethod(msg.getHttpMethod());
String parameterName = msg.getParameterName() != null ? msg.getParameterName() : "";

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

parameterName 的默认值不能退化成空字符串。

ApiMessageInfo.parameterName 的约定默认值是 "params",这里写成 "" 会让未显式声明 parameterName 的 API 生成出错误的请求包裹层。

💡 建议修改
-        String parameterName = msg.getParameterName() != null ? msg.getParameterName() : "";
+        String parameterName = msg.getParameterName() != null ? msg.getParameterName() : "params";

同样的修改需要应用到 generateQueryActionContent() 里的对应分支。

Also applies to: 187-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java`
around lines 79 - 82, The default for ApiMessageInfo.parameterName must be
"params" instead of an empty string; update the initialization of parameterName
in SdkGenerator (where boolean needSession and needPoll are computed) to use
msg.getParameterName() != null ? msg.getParameterName() : "params", and make the
analogous change in the generateQueryActionContent() branch that currently falls
back to "" so both action generators produce the correct request wrapper when
parameterName is unspecified.

Comment on lines +268 to +280
private String generateResultContent(ApiMessageInfo msg) {
// Non-query result: single 'value' field of the response type is already
// handled by the Action wrapping — the Result class itself holds inventory
// fields from the actual API response class. Since we don't have field info
// for the response class here, we emit an empty result (matches pattern where
// response fields come from the compiled response class at runtime).
String resultName = msg.getResultName();

StringBuilder sb = new StringBuilder();
sb.append("package org.zstack.sdk;\n\n");
sb.append("public class ").append(resultName).append(" {\n");
sb.append("}\n");
return sb.toString();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

非查询 Result 不能一律生成为空类。

makeResult() 会把响应反序列化到这里生成的 *Result 类型;当前实现完全丢掉了 APIReply/APIEvent 的字段,带 inventory 或其他返回值的新 API 在 SDK 里会变成“只能知道成功/失败,拿不到实际结果”。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java`
around lines 268 - 280, The generated Result class is currently always empty,
losing APIReply/APIEvent fields so makeResult() cannot deserialize real response
data; update generateResultContent(ApiMessageInfo msg) to emit the actual fields
from the response/reply/event description available on ApiMessageInfo (iterate
its field descriptors, map their types to Java SDK types, and append
corresponding private fields and getters/setters into the class body for
resultName) and only emit an empty class when no fields are present; ensure the
produced class shape matches what makeResult() expects for deserialization and
include any necessary imports or nested inventory type references when writing
the class text.

Comment on lines +322 to +331
if (param.getValidValues() != null && param.getValidValues().length > 0) {
sb.append(", validValues = {");
for (int i = 0; i < param.getValidValues().length; i++) {
if (i > 0) sb.append(", ");
sb.append("\"").append(param.getValidValues()[i]).append("\"");
}
sb.append("}");
}
if (param.getValidRegexValues() != null && !param.getValidRegexValues().isEmpty()) {
sb.append(", validRegexValues = \"").append(param.getValidRegexValues()).append("\"");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd SdkGenerator.java

Repository: MatheMatrix/zstack

Length of output: 138


🏁 Script executed:

wc -l zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java

Repository: MatheMatrix/zstack

Length of output: 142


🏁 Script executed:

sed -n '310,340p' zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java

Repository: MatheMatrix/zstack

Length of output: 1477


🏁 Script executed:

fd ApiParamInfo.java

Repository: MatheMatrix/zstack

Length of output: 134


🏁 Script executed:

cat -n zstack-dev-tool/src/main/java/org/zstack/devtool/model/ApiParamInfo.java

Repository: MatheMatrix/zstack

Length of output: 2735


🏁 Script executed:

rg "setValidRegexValues|setValidValues" -B 3 -A 3 zstack-dev-tool/

Repository: MatheMatrix/zstack

Length of output: 4509


🏁 Script executed:

sed -n '1,100p' zstack-dev-tool/src/main/java/org/zstack/devtool/scanner/ApiMessageScanner.java | head -80

Repository: MatheMatrix/zstack

Length of output: 3526


🏁 Script executed:

sed -n '150,250p' zstack-dev-tool/src/main/java/org/zstack/devtool/scanner/ApiMessageScanner.java

Repository: MatheMatrix/zstack

Length of output: 4673


🏁 Script executed:

sed -n '1,50p' zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java

Repository: MatheMatrix/zstack

Length of output: 1724


🏁 Script executed:

sed -n '280,343p' zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java

Repository: MatheMatrix/zstack

Length of output: 2885


🏁 Script executed:

rg "validValues\s*=|validRegexValues\s*=" plugin/ --type java -B 2 -A 2 | head -60

Repository: MatheMatrix/zstack

Length of output: 7276


🏁 Script executed:

sed -n '100,280p' zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java | grep -n "append\|\.toString()"

Repository: MatheMatrix/zstack

Length of output: 7806


🏁 Script executed:

sed -n '50,100p' zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java

Repository: MatheMatrix/zstack

Length of output: 2539


🏁 Script executed:

python3 << 'EOF'
# Check if there are any examples in the codebase where special characters
# could appear in validValues or validRegexValues
import subprocess
import re

# Search for validRegexValues usage to understand what patterns are used
result = subprocess.run(
    ['rg', 'validRegexValues', '-A 1', '-B 1', 'plugin/', '-t', 'java'],
    capture_output=True,
    text=True,
    cwd='.'
)

print("=== Found validRegexValues usages ===")
print(result.stdout[:2000])
EOF

Repository: MatheMatrix/zstack

Length of output: 100


🏁 Script executed:

rg "validRegexValues\s*=" -A 1 -B 1 --type java

Repository: MatheMatrix/zstack

Length of output: 18473


对注解字符串进行转义后再拼接到生成源码中。

validValuesvalidRegexValues 直接拼接到 Java 字符串字面量,当值中包含 \"、换行符等特殊字符时,会导致生成的代码无法编译或改变正则表达式的语义。例如,来自源码的 (\\d{4}) 经过 JavaParser 解析得到 (\d{4})(实际字符串),直接拼接后生成的 validRegexValues = "(\d{4})" 是无效的 Java 代码。

💡 建议修改
+    private String escapeJavaString(String value) {
+        return value
+                .replace("\\", "\\\\")
+                .replace("\"", "\\\"")
+                .replace("\n", "\\n")
+                .replace("\r", "\\r")
+                .replace("\t", "\\t");
+    }
+
     private String buildParamAnnotation(ApiParamInfo param) {
         StringBuilder sb = new StringBuilder();
@@
         if (param.getValidValues() != null && param.getValidValues().length > 0) {
             sb.append(", validValues = {");
             for (int i = 0; i < param.getValidValues().length; i++) {
                 if (i > 0) sb.append(", ");
-                sb.append("\"").append(param.getValidValues()[i]).append("\"");
+                sb.append("\"").append(escapeJavaString(param.getValidValues()[i])).append("\"");
             }
             sb.append("}");
         }
         if (param.getValidRegexValues() != null && !param.getValidRegexValues().isEmpty()) {
-            sb.append(", validRegexValues = \"").append(param.getValidRegexValues()).append("\"");
+            sb.append(", validRegexValues = \"")
+              .append(escapeJavaString(param.getValidRegexValues()))
+              .append("\"");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@zstack-dev-tool/src/main/java/org/zstack/devtool/generator/SdkGenerator.java`
around lines 322 - 331, The code appends raw values from param.getValidValues()
and param.getValidRegexValues() into generated Java string literals, which
breaks compilation and regex semantics when values contain backslashes, quotes,
or control chars; fix by escaping these strings before concatenation (e.g.,
create and call an escapeForJavaStringLiteral(String) helper used when appending
param.getValidValues()[i] and when appending param.getValidRegexValues()),
ensuring backslashes become "\\" , quotes become "\"" and newlines/tabs/carriage
returns/formfeeds are escaped so the generated source is valid Java and regexes
keep their intended backslash escapes.

Resolves: ZSTAC-0

Change-Id: I08e362d658b48a76ecbf51eaaa45fc32981c25b7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants