@ohos-cpf/3rdloop 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/README.md +25 -0
  2. package/lib/cli.js +22 -5
  3. package/lib/doctor.js +111 -10
  4. package/lib/mcp.js +498 -0
  5. package/package.json +6 -2
  6. package/vendor/Archive/data/.index/meta.json +7 -0
  7. package/vendor/Archive/data/.index/search.db +0 -0
  8. package/vendor/Archive/data/docs/experiences/arkts-check-tool-pitfalls-baseline-comparison.md +13 -0
  9. package/vendor/Archive/data/docs/experiences/arkts-deep-path-import-recognition.md +31 -0
  10. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-entry-file-case-mismatch.md +25 -0
  11. package/vendor/Archive/data/docs/experiences/arkts-demo-coverage-import-trailing-slash.md +20 -0
  12. package/vendor/Archive/data/docs/experiences/arkts-library-source-files-may-be-ts.md +18 -0
  13. package/vendor/Archive/data/docs/experiences/arkts-typedarray-buffer-interface-cast-compile-fixes.md +36 -0
  14. package/vendor/Archive/data/docs/experiences/arkts-xts-compile-error-clustering-fix.md +24 -0
  15. package/vendor/Archive/data/docs/experiences/arkts-xts-doc-expected-value-runtime-probe.md +35 -0
  16. package/vendor/Archive/data/docs/experiences/arkts-xts-fixture-rawfile-testability-injection.md +33 -0
  17. package/vendor/Archive/data/docs/experiences/code-linter-chinese-comment-false-positive.md +16 -0
  18. package/vendor/Archive/data/docs/experiences/esobject-new-expression-variable-tracking.md +33 -0
  19. package/vendor/Archive/data/docs/experiences/filesystem-case-sensitivity-module-resolution.md +31 -0
  20. package/vendor/Archive/data/docs/experiences/gitcode-clone-redirect-warning-normal.md +13 -0
  21. package/vendor/Archive/data/docs/experiences/kn_msgvq1w585ec8319.md +77 -0
  22. package/vendor/Archive/data/docs/experiences/kn_msovnm6rd46de72c.md +98 -0
  23. package/vendor/Archive/data/docs/experiences/kn_mspt8vntfe708576.md +87 -0
  24. package/vendor/Archive/data/docs/experiences/kn_mspu1ei77adbbb48.md +133 -0
  25. package/vendor/Archive/data/docs/experiences/kn_mspubgak8d30bfce.md +128 -0
  26. package/vendor/Archive/data/docs/experiences/kn_mspug2de3cb1f7c3.md +115 -0
  27. package/vendor/Archive/data/docs/experiences/kn_skill_opt_001.md +38 -0
  28. package/vendor/Archive/data/docs/experiences/loop-artifacts-verification-evidence-triangle.md +19 -0
  29. package/vendor/Archive/data/docs/experiences/macos-bash-replace-build-ps1.md +19 -0
  30. package/vendor/Archive/data/docs/experiences/macos-bsd-diff-q-illegal-option.md +15 -0
  31. package/vendor/Archive/data/docs/experiences/migrate-cjs-compatible-api-version-map-limit.md +17 -0
  32. package/vendor/Archive/data/docs/experiences/mp3agic-arkts-library-behavior-quirks.md +21 -0
  33. package/vendor/Archive/data/docs/experiences/ohos-face-detection-scan-api-mapping.md +15 -0
  34. package/vendor/Archive/data/docs/experiences/pr-metadata-and-diff-integrity-verification.md +12 -0
  35. package/vendor/Archive/data/docs/experiences/rn-coverage-index-signature-pseudo-member-false-negative.md +32 -0
  36. package/vendor/Archive/data/docs/experiences/rn-memory-check-severity-calibration.md +21 -0
  37. package/vendor/Archive/data/docs/experiences/rn-platform-specific-api-exclusion-evidence.md +24 -0
  38. package/vendor/Archive/data/docs/experiences/rn-upstream-version-diff-pitfalls.md +17 -0
  39. package/vendor/Archive/data/docs/experiences/rn-version-gap-risk-rating-rules.md +13 -0
  40. package/vendor/Archive/data/docs/experiences/rn-view-lib-native-contract-diff-fabric-spec.md +16 -0
  41. package/vendor/Archive/data/docs/experiences/rnoh-adapter-package-version-field-semantics.md +18 -0
  42. package/vendor/Archive/data/docs/experiences/rnoh-arkts-sqlite-rows-array-format.md +18 -0
  43. package/vendor/Archive/data/docs/experiences/rnoh-dependency-residue-scan-strategy.md +18 -0
  44. package/vendor/Archive/data/docs/experiences/rnoh-example-local-lib-dep-use-tgz.md +53 -0
  45. package/vendor/Archive/data/docs/experiences/rnoh-example-prereserve-rntesterlist.md +17 -0
  46. package/vendor/Archive/data/docs/experiences/rnoh-example-template-compatiblesdkversion-upgrade.md +29 -0
  47. package/vendor/Archive/data/docs/experiences/rnoh-example-turbomodule-shell-adaptation-checklist.md +41 -0
  48. package/vendor/Archive/data/docs/experiences/rnoh-mcp-build-project-tool-risk.md +18 -0
  49. package/vendor/Archive/data/docs/experiences/rnoh-npm-devdependencies-version-precheck.md +29 -0
  50. package/vendor/Archive/data/docs/experiences/rnoh-repo-baseline-version-verification.md +13 -0
  51. package/vendor/Archive/data/docs/experiences/rnoh-repo-upstream-directory-mapping.md +18 -0
  52. package/vendor/Archive/data/docs/experiences/rnoh-stateless-library-zero-leak-verification.md +31 -0
  53. package/vendor/Archive/data/docs/experiences/rnoh-template-npmrc-credential-removal.md +15 -0
  54. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-codegen-wiring-verification.md +19 -0
  55. package/vendor/Archive/data/docs/experiences/rnoh-turbomodule-cpp-methodmap-compatibility-check.md +20 -0
  56. package/vendor/Archive/data/docs/experiences/scenario-doc-vs-source-signature-diff-four-patterns.md +24 -0
  57. package/vendor/Archive/data/docs/experiences/static-analysis-grep-cross-validation.md +29 -0
  58. package/vendor/Archive/demo.js +489 -0
  59. package/vendor/Archive/index.js +43 -0
  60. package/vendor/Archive/package.json +16 -0
  61. package/vendor/Archive/src/constants.js +83 -0
  62. package/vendor/Archive/src/data/stopwords.txt +110 -0
  63. package/vendor/Archive/src/data/synonyms.json +25 -0
  64. package/vendor/Archive/src/data/terms.txt +59 -0
  65. package/vendor/Archive/src/db/sqlite-backend.js +312 -0
  66. package/vendor/Archive/src/db/sqlite-schema.js +64 -0
  67. package/vendor/Archive/src/engine.js +501 -0
  68. package/vendor/Archive/src/index/index-manager.js +203 -0
  69. package/vendor/Archive/src/index/index-state.js +94 -0
  70. package/vendor/Archive/src/index/search-text-builder.js +106 -0
  71. package/vendor/Archive/src/index.js +22 -0
  72. package/vendor/Archive/src/parser/api-identifiers.js +121 -0
  73. package/vendor/Archive/src/parser/api-suffixes.js +48 -0
  74. package/vendor/Archive/src/parser/heading-normalize.js +40 -0
  75. package/vendor/Archive/src/parser/markdown-splitter.js +215 -0
  76. package/vendor/Archive/src/search/catalog-routing.js +87 -0
  77. package/vendor/Archive/src/search/fts-search.js +289 -0
  78. package/vendor/Archive/src/search/query-normalizer.js +159 -0
  79. package/vendor/Archive/src/search/query-rewriter.js +72 -0
  80. package/vendor/Archive/src/search/search-engine.js +72 -0
  81. package/vendor/Archive/src/search/snippet.js +64 -0
  82. package/vendor/Archive/src/store/dir-store.js +103 -0
  83. package/vendor/Archive/src/store/doc-store.js +36 -0
  84. package/vendor/Archive/src/tokenizer/lexicon.js +151 -0
  85. package/vendor/Archive/src/tokenizer/tokenizer.js +167 -0
  86. package/vendor/Archive/src/types.js +86 -0
  87. package/vendor/Archive/src/utils/hash.js +16 -0
  88. package/vendor/Archive/src/utils/lock.js +73 -0
  89. package/vendor/Archive/src/utils/path-safety.js +96 -0
  90. package/vendor/Archive/src/utils/paths.js +39 -0
  91. package/vendor/BadCase/rn/INDEX.md +127 -0
  92. package/vendor/BadCase/rn/cases/KI-001-webview/350/247/206/351/242/221/346/227/240/346/263/225/346/250/252/345/261/217.md +52 -0
  93. package/vendor/BadCase/rn/cases/KI-002-fastImage/347/203/255/346/233/264/346/226/260/345/233/276/347/211/207/345/244/261/350/264/245.md +37 -0
  94. package/vendor/BadCase/rn/cases/KI-003-wechat/347/274/251/347/225/245/345/233/276/346/234/254/345/234/260/345/233/276/347/211/207.md +62 -0
  95. package/vendor/BadCase/rn/cases/KI-004-wechat/345/233/236/350/260/203/346/227/266/345/272/217.md +47 -0
  96. package/vendor/BadCase/rn/cases/KI-005-geolocation/345/257/274/345/205/245/350/267/257/345/276/204/345/220/216/347/274/200.md +31 -0
  97. package/vendor/BadCase/rn/cases/KI-006-SmartRefreshLayout/346/231/272/350/203/275/346/214/207/351/222/210/345/264/251/346/272/203.md +55 -0
  98. package/vendor/BadCase/rn/cases/KI-007-orientation-locker/344/274/240/346/204/237/345/231/250/345/210/235/345/247/213/345/214/226.md +58 -0
  99. package/vendor/BadCase/rn/cases/KI-008-orientation-locker/344/274/240/346/204/237/345/231/250/345/274/225/347/224/250/350/256/241/346/225/260.md +47 -0
  100. package/vendor/BadCase/rn/cases/KI-009-pager-view/345/265/214/345/245/227/346/273/221/345/212/250.md +38 -0
  101. package/vendor/BadCase/rn/cases/KI-010-screens/346/211/213/345/212/277/345/206/262/347/252/201.md +48 -0
  102. package/vendor/BadCase/rn/cases/KI-011-spring-scrollview/346/211/213/345/212/277/351/207/215/345/244/215/347/273/221/345/256/232.md +49 -0
  103. package/vendor/BadCase/rn/cases/KI-012-qr-camera/346/250/252/345/261/217/351/200/202/351/205/215.md +59 -0
  104. package/vendor/BadCase/rn/cases/KI-013-webview/346/267/261/350/211/262/346/250/241/345/274/217/350/203/214/346/231/257/350/211/262.md +37 -0
  105. package/vendor/BadCase/rn/cases/KI-014-amap-geolocation/345/233/236/350/260/203/347/274/272/345/244/261.md +42 -0
  106. package/vendor/BadCase/rn/cases/KI-015-reanimated/350/277/220/350/241/214/346/227/266/346/236/220/346/236/204/345/264/251/346/272/203.md +54 -0
  107. package/vendor/BadCase/rn/cases/KI-016-fs/346/226/207/344/273/266/350/257/273/345/217/226/347/274/226/347/240/201.md +58 -0
  108. package/vendor/BadCase/rn/cases/KI-017-blob-util/345/217/202/346/225/260/347/274/226/347/240/201/351/224/231/350/257/257.md +39 -0
  109. package/vendor/BadCase/rn/cases/KI-018-modal/345/274/271/347/252/227/351/227/252/347/203/201.md +53 -0
  110. package/vendor/BadCase/rn/cases/KI-019-code-push/351/207/215/345/220/257/351/200/273/350/276/221/345/244/261/346/225/210.md +79 -0
  111. package/vendor/BadCase/rn/cases/KI-020-netinfo/346/225/217/346/204/237/344/277/241/346/201/257/346/263/204/351/234/262.md +60 -0
  112. package/vendor/BadCase/rn/cases/KI-021-video/346/234/254/345/234/260/350/247/206/351/242/221/346/222/255/346/224/276/345/274/202/345/270/270.md +79 -0
  113. package/vendor/BadCase/rn/cases/KI-022-audio-recorder-player/346/236/232/344/270/276/347/274/272/345/244/261.md +38 -0
  114. package/vendor/BadCase/rn/cases/KI-023-svg/346/270/220/345/217/230/347/273/247/346/211/277/345/244/261/346/225/210.md +62 -0
  115. package/vendor/BadCase/rn/generalization-log.md +55 -0
  116. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.json +328 -0
  117. package/vendor/BadCase/rn/pr-info/rntpc_react-native-SmartRefreshLayout-32.md +382 -0
  118. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.json +1351 -0
  119. package/vendor/BadCase/rn/pr-info/rntpc_react-native-amap-geolocation-27.md +1176 -0
  120. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.json +538 -0
  121. package/vendor/BadCase/rn/pr-info/rntpc_react-native-audio-recorder-player-22.md +1488 -0
  122. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.json +211 -0
  123. package/vendor/BadCase/rn/pr-info/rntpc_react-native-blob-util-23.md +35 -0
  124. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.json +2048 -0
  125. package/vendor/BadCase/rn/pr-info/rntpc_react-native-code-push-17.md +3866 -0
  126. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.json +198 -0
  127. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fast-image-102.md +40 -0
  128. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.json +751 -0
  129. package/vendor/BadCase/rn/pr-info/rntpc_react-native-fs-38.md +883 -0
  130. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.json +1544 -0
  131. package/vendor/BadCase/rn/pr-info/rntpc_react-native-geolocation-24.md +2598 -0
  132. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.json +246 -0
  133. package/vendor/BadCase/rn/pr-info/rntpc_react-native-modal-20.md +554 -0
  134. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.json +211 -0
  135. package/vendor/BadCase/rn/pr-info/rntpc_react-native-netinfo-31.md +54 -0
  136. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.json +1626 -0
  137. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-45.md +2213 -0
  138. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.json +208 -0
  139. package/vendor/BadCase/rn/pr-info/rntpc_react-native-orientation-locker-51.md +77 -0
  140. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.json +377 -0
  141. package/vendor/BadCase/rn/pr-info/rntpc_react-native-pager-view-32.md +174 -0
  142. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.json +232 -0
  143. package/vendor/BadCase/rn/pr-info/rntpc_react-native-qr-decode-image-camera-37.md +194 -0
  144. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.json +198 -0
  145. package/vendor/BadCase/rn/pr-info/rntpc_react-native-reanimated-82.md +67 -0
  146. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.json +198 -0
  147. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-87.md +38 -0
  148. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.json +208 -0
  149. package/vendor/BadCase/rn/pr-info/rntpc_react-native-screens-93.md +34 -0
  150. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.json +222 -0
  151. package/vendor/BadCase/rn/pr-info/rntpc_react-native-spring-scrollview-38.md +56 -0
  152. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.json +463 -0
  153. package/vendor/BadCase/rn/pr-info/rntpc_react-native-svg-117.md +383 -0
  154. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.json +464 -0
  155. package/vendor/BadCase/rn/pr-info/rntpc_react-native-video-66.md +565 -0
  156. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.json +198 -0
  157. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-112.md +47 -0
  158. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.json +232 -0
  159. package/vendor/BadCase/rn/pr-info/rntpc_react-native-webview-121.md +422 -0
  160. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.json +295 -0
  161. package/vendor/BadCase/rn/pr-info/rntpc_react-native-wechat-lib-15.md +1038 -0
  162. package/vendor/BadCase/rn/rules/R-01-/345/205/250/345/261/217/347/252/227/345/217/243/347/212/266/346/200/201/347/256/241/347/220/206/345/256/214/346/225/264/346/200/247.md +20 -0
  163. package/vendor/BadCase/rn/rules/R-02-/350/265/204/346/272/220URI/345/275/242/346/200/201/345/210/206/346/224/257/350/246/206/347/233/226.md +21 -0
  164. package/vendor/BadCase/rn/rules/R-03-/351/231/204/345/261/236/346/223/215/344/275/234/345/244/261/350/264/245/351/230/273/346/226/255/344/270/273/346/265/201/347/250/213.md +20 -0
  165. package/vendor/BadCase/rn/rules/R-04-/345/274/202/346/255/245/345/233/236/350/260/203/346/263/250/345/206/214/346/227/266/345/272/217.md +20 -0
  166. package/vendor/BadCase/rn/rules/R-05-/346/250/241/345/235/227/345/257/274/345/205/245/350/267/257/345/276/204/350/247/204/350/214/203.md +19 -0
  167. package/vendor/BadCase/rn/rules/R-06-/344/274/240/346/204/237/345/231/250/347/241/254/344/273/266/346/235/203/351/231/220/346/214/211/351/234/200/350/216/267/345/217/226.md +21 -0
  168. package/vendor/BadCase/rn/rules/R-07-/346/231/272/350/203/275/346/214/207/351/222/210/347/224/237/345/221/275/345/221/250/346/234/237/347/256/241/347/220/206.md +20 -0
  169. package/vendor/BadCase/rn/rules/R-08-/346/211/213/345/212/277/344/272/213/344/273/266/346/263/250/345/206/214/344/270/216/345/206/262/347/252/201/345/244/204/347/220/206.md +22 -0
  170. package/vendor/BadCase/rn/rules/R-09-/346/250/252/347/253/226/345/261/217/345/244/232/345/275/242/346/200/201/345/270/203/345/261/200/351/200/202/351/205/215.md +20 -0
  171. package/vendor/BadCase/rn/rules/R-10-/346/267/261/350/211/262/346/250/241/345/274/217/344/270/273/351/242/230/351/242/234/350/211/262/351/200/202/351/205/215.md +19 -0
  172. package/vendor/BadCase/rn/rules/R-11-/346/216/245/345/217/243/345/233/236/350/260/203/345/256/236/347/216/260/345/256/214/346/225/264/346/200/247.md +20 -0
  173. package/vendor/BadCase/rn/rules/R-12-/345/216/237/347/224/237/350/277/220/350/241/214/346/227/266/350/265/204/346/272/220/346/236/220/346/236/204/346/227/266/345/272/217.md +20 -0
  174. package/vendor/BadCase/rn/rules/R-13-/346/226/207/344/273/266/346/225/260/346/215/256/347/274/226/347/240/201/344/270/216/346/240/274/345/274/217/345/205/274/345/256/271.md +21 -0
  175. package/vendor/BadCase/rn/rules/R-14-/345/212/250/347/224/273/345/210/235/345/247/213/345/214/226/346/227/266/345/272/217/344/270/216/347/212/266/346/200/201/345/220/214/346/255/245.md +20 -0
  176. package/vendor/BadCase/rn/rules/R-15-/347/212/266/346/200/201/346/214/201/344/271/205/345/214/226/344/270/216/351/207/215/345/220/257/351/200/273/350/276/221/344/270/200/350/207/264/346/200/247.md +20 -0
  177. package/vendor/BadCase/rn/rules/R-16-/346/225/217/346/204/237/344/277/241/346/201/257/346/227/245/345/277/227/346/263/204/351/234/262.md +19 -0
  178. package/vendor/BadCase/rn/rules/R-17-/345/252/222/344/275/223/350/265/204/346/272/220/347/224/237/345/221/275/345/221/250/346/234/237/344/270/216/351/224/231/350/257/257/345/244/204/347/220/206.md +21 -0
  179. package/vendor/BadCase/rn/rules/R-18-/350/203/275/345/212/233/346/236/232/344/270/276/351/205/215/347/275/256/345/256/214/346/225/264/346/200/247.md +19 -0
  180. package/vendor/BadCase/rn/rules/R-19-/345/233/276/345/275/242/346/270/262/346/237/223/345/261/236/346/200/247/347/273/247/346/211/277/344/270/216/351/207/215/347/275/256.md +19 -0
  181. package/vendor/MCP/README.md +490 -0
  182. package/vendor/MCP/config.json +29 -0
  183. package/vendor/MCP/package.json +23 -0
  184. package/vendor/MCP/scripts/analyze-demo-coverage.cjs +1203 -0
  185. package/vendor/MCP/scripts/analyze-xts-coverage.cjs +1245 -0
  186. package/vendor/MCP/scripts/deveco_docs.js +247 -0
  187. package/vendor/MCP/scripts/extract-interfaces.cjs +1487 -0
  188. package/vendor/MCP/scripts/flutter-analyze-demo-coverage.cjs +666 -0
  189. package/vendor/MCP/scripts/flutter-analyze-xts-coverage.cjs +684 -0
  190. package/vendor/MCP/scripts/flutter-extract-interfaces.cjs +1251 -0
  191. package/vendor/MCP/scripts/flutter-render-spec-doc.cjs +351 -0
  192. package/vendor/MCP/scripts/render-spec-doc.cjs +347 -0
  193. package/vendor/MCP/scripts/rn-analyze-demo-coverage.cjs +645 -0
  194. package/vendor/MCP/scripts/rn-analyze-test-coverage.cjs +614 -0
  195. package/vendor/MCP/scripts/rn-extract-interfaces.cjs +1462 -0
  196. package/vendor/MCP/scripts/rn-render-spec-doc.cjs +327 -0
  197. package/vendor/MCP/scripts/rn-scan-exports.cjs +1336 -0
  198. package/vendor/MCP/src/client/MCPClientPool.js +220 -0
  199. package/vendor/MCP/src/config/ConfigLoader.js +189 -0
  200. package/vendor/MCP/src/executor/ScriptExecutor.js +110 -0
  201. package/vendor/MCP/src/gateway/Gateway.js +451 -0
  202. package/vendor/MCP/src/index.js +87 -0
  203. package/vendor/MCP/src/library/KBToolProvider.js +229 -0
  204. package/vendor/MCP/src/library/logger.js +60 -0
  205. package/vendor/MCP/src/registry/ScriptRegistry.js +238 -0
  206. package/vendor/MCP/src/registry/ToolRegistry.js +111 -0
  207. package/vendor/MCP/src/types.js +115 -0
  208. package/vendor/Server/Skills/arkts-library-test-coverage-check/references/MCP_TOOL_USAGE.md +4 -2
  209. package/vendor/Server/Skills/arkts-library-xts-coverage/SKILL.md +3 -3
  210. package/vendor/Server/Skills/knowledge-import/SKILL.md +1 -1
  211. package/vendor/Server/Skills/knowledge-import/references/MCP_TOOL_USAGE.md +12 -4
  212. package/vendor/Server/Skills/ohos-lib-pr-push/SKILL.md +5 -4
  213. package/vendor/Server/Skills/rn-library-issue-generalizer/SKILL.md +2 -2
  214. package/vendor/Server/Skills/rn-library-known-issue-check/SKILL.md +2 -2
  215. package/vendor/Server/Skills/rnoh-lib-demo-coverage/SKILL.md +1 -2
  216. package/vendor/Server/Skills/rnoh-lib-test-align/references/port-test-demo-guide.md +1 -1
  217. package/vendor/Server/Skills/rnoh-lib-xts-coverage/SKILL.md +1 -2
  218. package/vendor/Server/package.json +5 -1
  219. package/vendor/VERSION +3 -3
@@ -0,0 +1,1487 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tool arkts_extract_interfaces
4
+ * @description 从鸿蒙 ArkTS 三方库源码目录提取接口规格。基于 TypeScript Compiler API 语义分析,提取类/接口/枚举/方法/属性/字段等导出接口信息,输出 JSON 或直接传给下游覆盖率分析。用于分析库的完整 API 表面。
5
+ * @param libroot: string - 库源码根目录路径(包含 library/ 子目录,其中有 Index.ets 入口文件)
6
+ * @param output: string - 输出 JSON 文件路径(可选,不指定则输出 JSON 到 stdout)
7
+ * @hidden
8
+ * extract-interfaces.cjs — 鸿蒙 ArkTS 三方库接口提取引擎(统一版)
9
+ *
10
+ * 合并了 parse-spec-ast.cjs(AST 语义分析)和 extract-interfaces.cjs(正则边缘场景)
11
+ * 两套方案的优势,作为接口规格文档生成、XTS 覆盖率、Demo 覆盖率的统一数据源。
12
+ *
13
+ * 核心策略:
14
+ * 1. TS Compiler API 语义分析为引擎核心(类型推断、导出可见性、JSDoc)
15
+ * 2. .ets → .ts 虚拟路径映射 + struct→class 文本替换
16
+ * 3. .so 模块 types/ 目录搜索 + declare var 静态方法提取
17
+ * 4. export default / export { } 转发导出追踪
18
+ * 5. 内部类展开(static Builder = new InnerBuilder())
19
+ * 6. 深度递归搜索 Index.ets(≤4 层)
20
+ *
21
+ * 用法:
22
+ * node extract-interfaces.cjs --repoDir <库源码目录> --output <interface-spec.json>
23
+ *
24
+ * 使用示例:
25
+ * # 从库源码目录提取接口,输出 JSON 中间件
26
+ * node extract-interfaces.cjs --repoDir /path/to/lottie_turbo --output lottie_turbo-interface-spec.json
27
+ *
28
+ * # 无 --output 时输出到 stdout
29
+ * node extract-interfaces.cjs --repoDir /path/to/lottie_turbo
30
+ *
31
+ * # 库源码目录结构约定:
32
+ * # /path/to/lottie_turbo/
33
+ * # ├── library/ ← 库源码(Index.ets 入口)
34
+ * # │ ├── Index.ets
35
+ * # │ ├── src/main/ets/
36
+ * # │ └── src/main/cpp/types/.../Index.d.ts ← Native .so 声明
37
+ * # └── entry/src/main/ets/ ← Demo 源码
38
+ *
39
+ * 输出 JSON 数据模型见 INTERFACE_SCHEMA.md
40
+ *
41
+ * 模块导出:
42
+ * const { generateSpec } = require('./extract-interfaces.cjs');
43
+ * const spec = generateSpec('/path/to/lottie_turbo'); // 直接返回内存对象,无需写 JSON
44
+ */
45
+
46
+ const fs = require('fs');
47
+ const path = require('path');
48
+
49
+ // ── TypeScript 加载(自动查找)─────────────────────────────
50
+ function loadTypeScript() {
51
+ // 最优先:libraryTestScan.js 通过环境变量注入的精确路径
52
+ if (process.env.TYPESCRIPT_PATH) {
53
+ try {
54
+ const ts = require(process.env.TYPESCRIPT_PATH);
55
+ if (ts && typeof ts.createProgram === 'function') return ts;
56
+ } catch (e) { /* continue */ }
57
+ }
58
+ const candidates = [
59
+ path.join(__dirname, '..', 'node_modules', 'typescript'),
60
+ path.join(__dirname, 'node_modules', 'typescript'),
61
+ // fallback: 从 XTS Skill 目录查找
62
+ path.join(__dirname, '..', '..', '3rdLibraryLoop', 'Server', 'Skills', 'ArkTsLibraryXTSCoverageSkill', 'node_modules', 'typescript'),
63
+ path.join(__dirname, '..', 'Server', 'Skills', 'ArkTsLibraryXTSCoverageSkill', 'node_modules', 'typescript'),
64
+ ];
65
+ for (const p of candidates) {
66
+ try {
67
+ const ts = require(p);
68
+ if (ts && typeof ts.createProgram === 'function') return ts;
69
+ } catch (e) { /* continue */ }
70
+ }
71
+ // 最后尝试全局 require
72
+ try {
73
+ const ts = require('typescript');
74
+ if (ts && typeof ts.createProgram === 'function') return ts;
75
+ } catch (e) { /* continue */ }
76
+ console.error('错误: 无法加载 typescript 模块。请运行 npm install typescript');
77
+ process.exit(1);
78
+ }
79
+
80
+ const ts = loadTypeScript();
81
+
82
+ // ── 参数解析 ──────────────────────────────────────────────
83
+
84
+ function parseArgs() {
85
+ const args = process.argv.slice(2);
86
+ // MCP Gateway 模式:第一个参数是 JSON 字符串
87
+ if (args.length > 0 && args[0].startsWith('{')) {
88
+ try { return JSON.parse(args[0]); } catch (e) { /* 降级到 CLI 模式 */ }
89
+ }
90
+ const parsed = {};
91
+ for (let i = 0; i < args.length; i++) {
92
+ if (args[i].startsWith('--')) {
93
+ const key = args[i].substring(2);
94
+ if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
95
+ parsed[key] = args[i + 1];
96
+ i++;
97
+ } else {
98
+ parsed[key] = true;
99
+ }
100
+ }
101
+ }
102
+ return parsed;
103
+ }
104
+
105
+ // ── 工具函数 ──────────────────────────────────────────────
106
+
107
+ /** 递归收集某个 glob(简单 ** 支持)匹配的文件 */
108
+ function glob(baseDir, pattern) {
109
+ const results = [];
110
+ const parts = pattern.split('/');
111
+ const rec = (dir, idx) => {
112
+ if (idx >= parts.length) {
113
+ if (fs.existsSync(dir) && fs.statSync(dir).isFile()) results.push(dir);
114
+ return;
115
+ }
116
+ const part = parts[idx];
117
+ if (part === '**') {
118
+ rec(dir, idx + 1);
119
+ if (!fs.existsSync(dir)) return;
120
+ for (const entry of fs.readdirSync(dir)) {
121
+ const full = path.join(dir, entry);
122
+ let st;
123
+ try { st = fs.statSync(full); } catch (e) { continue; }
124
+ if (st.isDirectory()) {
125
+ if (entry === '.git' || entry === 'node_modules' || entry === 'oh_modules' || entry === 'build' || entry === '.hvigor') continue;
126
+ rec(full, idx);
127
+ rec(full, idx + 1);
128
+ }
129
+ }
130
+ return;
131
+ }
132
+ if (!fs.existsSync(dir)) return;
133
+ for (const entry of fs.readdirSync(dir)) {
134
+ if (part.includes('*')) {
135
+ const re = new RegExp('^' + part.split('*').map((s) => s.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('.*') + '$', 'i');
136
+ if (re.test(entry)) {
137
+ rec(path.join(dir, entry), idx + 1);
138
+ }
139
+ continue;
140
+ }
141
+ if (entry.toLowerCase() === part.toLowerCase()) {
142
+ rec(path.join(dir, entry), idx + 1);
143
+ }
144
+ }
145
+ };
146
+ rec(baseDir, 0);
147
+ return results;
148
+ }
149
+
150
+ /** 获取某文件相对 repoDir 的 posix 风格相对路径 */
151
+ function rel(filePath, repoDir) {
152
+ let r = path.relative(repoDir, filePath).split('\\').join('/');
153
+ if (!r.startsWith('.')) r = './' + r;
154
+ return r;
155
+ }
156
+
157
+ /** 读取文件文本,兼容 BOM */
158
+ function readText(file) {
159
+ return fs.readFileSync(file, 'utf-8').replace(/^\uFEFF/, '');
160
+ }
161
+
162
+ /** 从源码行列表中提取 JSDoc 描述(向上回溯注释,作为正则方案补充) */
163
+ function extractDocFromLines(lines, idx, maxLookback = 8) {
164
+ const docLines = [];
165
+ let j = idx - 1;
166
+ while (j >= 0 && idx - j <= maxLookback) {
167
+ const line = lines[j].trim();
168
+ if (line.startsWith('*/')) { docLines.unshift(line); j--; continue; }
169
+ if (line.startsWith('*') || line.startsWith('/**') || line.startsWith('/*')) {
170
+ docLines.unshift(line); j--; continue;
171
+ }
172
+ if (line.startsWith('//')) { docLines.unshift(line.replace(/^\/\/\s*/, '')); j--; continue; }
173
+ break;
174
+ }
175
+ const text = docLines.join(' ').replace(/\/\*|\*\//g, '').replace(/\s+/g, ' ').trim();
176
+ const filtered = text
177
+ .replace(/Copyright\s*\(c\)[^]*?License[^]*?(License\)|limitations[^.]*)/gi, '')
178
+ .replace(/#[036]477969|https?:\/\/\S+/g, '')
179
+ .replace(/\s+/g, ' ')
180
+ .trim();
181
+ if (/^(license|licensed under)/i.test(filtered)) return '-';
182
+ return filtered || '-';
183
+ }
184
+
185
+ // ── 入口文件定位 ────────────────────────────────────────────
186
+
187
+ /**
188
+ * 大小写校准:macOS/Windows 文件系统大小写不敏感,fs.existsSync('Index.ets')
189
+ * 对磁盘上实际名为 index.ets 的文件同样返回 true,导致返回的路径大小写可能与
190
+ * 磁盘真实大小写不一致;后续以该路径为 key 的精确匹配(Map/对象查找、TS 虚拟
191
+ * 路径解析)会静默失败。用 readdirSync 返回的真实文件名回写。
192
+ */
193
+ function toRealCase(filePath) {
194
+ try {
195
+ const dir = path.dirname(filePath);
196
+ const base = path.basename(filePath);
197
+ const entries = fs.readdirSync(dir);
198
+ if (entries.includes(base)) return filePath;
199
+ const real = entries.find((e) => e.toLowerCase() === base.toLowerCase());
200
+ if (real) return path.join(dir, real);
201
+ } catch (e) { /* ignore */ }
202
+ return filePath;
203
+ }
204
+
205
+ /**
206
+ * 定位入口 Index.ets(多级自适应搜索)
207
+ * 优先级: library/Index.ets > library/src/main/ets/Index.ets > {repoDir}/Index.ets
208
+ * Fallback: 深度递归搜索模块根目录(≤4 层)
209
+ * 注意:返回路径统一经 toRealCase 校准为磁盘真实大小写(Index.ets vs index.ets)
210
+ */
211
+ function locateEntryFile(repoDir) {
212
+ const candidates = [
213
+ path.join(repoDir, 'library', 'Index.ets'),
214
+ path.join(repoDir, 'library', 'src', 'main', 'ets', 'Index.ets'),
215
+ path.join(repoDir, 'Index.ets'),
216
+ ];
217
+ let entryFile = candidates.find((f) => fs.existsSync(f));
218
+ if (entryFile) return toRealCase(entryFile);
219
+
220
+ // 深度递归搜索
221
+ const deep = [];
222
+ const walkIndex = (dir, depth) => {
223
+ if (depth > 4) return;
224
+ let hasIndex = false;
225
+ for (const entry of fs.readdirSync(dir)) {
226
+ if (entry === '.git' || entry === 'node_modules' || entry === 'oh_modules' || entry === 'entry' || entry === 'AppScope' || entry === 'build') continue;
227
+ const full = path.join(dir, entry);
228
+ let stat;
229
+ try { stat = fs.statSync(full); } catch (e) { continue; }
230
+ if (stat.isDirectory()) {
231
+ walkIndex(full, depth + 1);
232
+ const idx = fs.existsSync(path.join(full, 'Index.ets'));
233
+ if (idx) deep.push(toRealCase(path.join(full, 'Index.ets')));
234
+ }
235
+ }
236
+ };
237
+ try { walkIndex(repoDir, 0); } catch (e) { /* ignore */ }
238
+ if (deep.length > 0) {
239
+ deep.sort((a, b) => a.length - b.length);
240
+ entryFile = deep.find((p) => fs.existsSync(path.join(path.dirname(p), 'oh-package.json5'))) || deep[0];
241
+ }
242
+ return entryFile ? toRealCase(entryFile) : null;
243
+ }
244
+
245
+ // ── 库名提取 ──────────────────────────────────────────────
246
+
247
+ /**
248
+ * 简单正则转义
249
+ */
250
+ function escapeRegexStr(str) {
251
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
252
+ }
253
+
254
+ /**
255
+ * 从 oh-package.json5 文件提取顶层 name 字段
256
+ * 注意:只匹配顶层 name(缩进 ≤2 空格),避免误匹配嵌套对象的 name 字段
257
+ * (如 author.name / contributor.name)
258
+ * @param {string} pkgPath
259
+ * @returns {string|null}
260
+ */
261
+ function readPkgName(pkgPath) {
262
+ if (!fs.existsSync(pkgPath)) return null;
263
+ try {
264
+ const raw = fs.readFileSync(pkgPath, 'utf-8');
265
+ const m = raw.match(/^\s{0,2}["']?name["']?\s*:\s*["']([^"']+)["']/m);
266
+ return m ? m[1] : null;
267
+ } catch (e) {
268
+ return null;
269
+ }
270
+ }
271
+
272
+ /**
273
+ * 从 entry/oh-package.json5 的 dependencies 反查指向 libDirName 的依赖名
274
+ * 当库自身 oh-package.json5 缺 name 字段时,消费方 dependencies 的 key
275
+ * 才是用户 import 时用的真实包名(如 @ohos/mqtt)
276
+ * @param {string} repoDir
277
+ * @param {string} libDirName 库模块目录名(如 ohos_Mqtt)
278
+ * @returns {string|null}
279
+ */
280
+ function findDepNameFromEntry(repoDir, libDirName) {
281
+ const entryPkgPath = path.join(repoDir, 'entry', 'oh-package.json5');
282
+ if (!fs.existsSync(entryPkgPath)) return null;
283
+ try {
284
+ const raw = fs.readFileSync(entryPkgPath, 'utf-8');
285
+ // 匹配 "依赖名": "file:../libDirName" 或 "file:../libDirName/..."
286
+ const depRegex = new RegExp(
287
+ `["']([^"']+)["']\\s*:\\s*["']file:\\.\\./${escapeRegexStr(libDirName)}(?:/[^"']*)?["']`
288
+ );
289
+ const m = raw.match(depRegex);
290
+ return m ? m[1] : null;
291
+ } catch (e) {
292
+ return null;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * 提取库的真实包名(用户 import 时用的名字)
298
+ *
299
+ * 查找优先级:
300
+ * 1. 库模块目录(entryFile 所在的最近含 oh-package.json5 的父目录)的 name 字段
301
+ * 2. {repoDir}/library/oh-package.json5 的 name 字段(标准结构向后兼容)
302
+ * 3. entry/oh-package.json5 的 dependencies 中指向库目录的依赖名
303
+ * (库自身缺 name 时,消费方声明的依赖名才是真实包名,如 @ohos/mqtt)
304
+ * 4. {repoDir}/oh-package.json5 的 name 字段(工程名兜底)
305
+ * 5. path.basename(repoDir)
306
+ *
307
+ * @param {string} repoDir
308
+ * @param {string=} entryFile 入口文件路径(用于定位库模块目录)
309
+ */
310
+ function extractLibraryName(repoDir, entryFile) {
311
+ const candidates = [];
312
+
313
+ // 1. entryFile 所在的最近含 oh-package.json5 的父目录
314
+ if (entryFile) {
315
+ let dir = path.dirname(entryFile);
316
+ for (let i = 0; i < 6 && dir && dir.startsWith(repoDir); i++) {
317
+ const pkgPath = path.join(dir, 'oh-package.json5');
318
+ if (fs.existsSync(pkgPath)) {
319
+ candidates.push(pkgPath);
320
+ break;
321
+ }
322
+ const parent = path.dirname(dir);
323
+ if (parent === dir) break;
324
+ dir = parent;
325
+ }
326
+ }
327
+
328
+ // 2. 标准结构
329
+ candidates.push(path.join(repoDir, 'library', 'oh-package.json5'));
330
+
331
+ // 先从库自身 oh-package.json5 取 name
332
+ for (const p of candidates) {
333
+ const name = readPkgName(p);
334
+ if (name) return name;
335
+ }
336
+
337
+ // 3. entry dependencies 反查(库自身缺 name 时的关键回退)
338
+ if (entryFile) {
339
+ const libDirName = path.basename(path.dirname(entryFile));
340
+ const depName = findDepNameFromEntry(repoDir, libDirName);
341
+ if (depName) return depName;
342
+ }
343
+
344
+ // 4. 根 oh-package.json5(工程名兜底)
345
+ const rootName = readPkgName(path.join(repoDir, 'oh-package.json5'));
346
+ if (rootName) return rootName;
347
+
348
+ // 5. basename 兜底
349
+ return path.basename(repoDir);
350
+ }
351
+
352
+ // ── 源文件收集 ──────────────────────────────────────────────
353
+
354
+ /**
355
+ * 收集库中所有 .d.ts / .ets / .ts 源文件路径
356
+ *
357
+ * 模块根目录定位策略(与 locateEntryFile 保持一致):
358
+ * 1. {libRoot}/library ← 标准结构(lottie_turbo 等),向后兼容
359
+ * 2. entryFile 向上最近的含 oh-package.json5 / oh-package.json 的目录
360
+ * ← 非标准结构(ohos_mqtt 用 ohos_Mqtt/ 作模块根)
361
+ * 3. entryFile 所在目录 ← 兜底
362
+ *
363
+ * @param {string} libRoot 库源码根目录
364
+ * @param {string} entryFile 入口 Index.ets 路径(用于定位非标准结构的模块根)
365
+ */
366
+ function collectSourceFiles(libRoot, entryFile) {
367
+ const sources = [];
368
+
369
+ // 定位模块根目录
370
+ let moduleRoot = path.join(libRoot, 'library');
371
+ if (!fs.existsSync(moduleRoot) && entryFile) {
372
+ let dir = path.dirname(entryFile);
373
+ for (let i = 0; i < 6 && dir && dir.startsWith(libRoot); i++) {
374
+ if (fs.existsSync(path.join(dir, 'oh-package.json5')) ||
375
+ fs.existsSync(path.join(dir, 'oh-package.json'))) {
376
+ moduleRoot = dir;
377
+ break;
378
+ }
379
+ const parent = path.dirname(dir);
380
+ if (parent === dir) break;
381
+ dir = parent;
382
+ }
383
+ // 兜底:entryFile 所在目录
384
+ if (!fs.existsSync(moduleRoot)) moduleRoot = path.dirname(entryFile);
385
+ }
386
+
387
+ function walk(dir) {
388
+ if (!fs.existsSync(dir)) return;
389
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
390
+ for (const entry of entries) {
391
+ const fullPath = path.join(dir, entry.name);
392
+ if (entry.isDirectory()) {
393
+ if (entry.name === 'node_modules' || entry.name === 'oh_modules' || entry.name === 'build') continue;
394
+ walk(fullPath);
395
+ } else if (entry.isFile()) {
396
+ if (entry.name.endsWith('.d.ts') || entry.name.endsWith('.ets') || entry.name.endsWith('.ts')) {
397
+ sources.push(fullPath);
398
+ }
399
+ }
400
+ }
401
+ }
402
+
403
+ walk(moduleRoot);
404
+ return sources;
405
+ }
406
+
407
+ // ── AST 辅助函数 ────────────────────────────────────────────
408
+
409
+ /** 获取 symbol 的 JSDoc 描述 */
410
+ function getJsDocDescription(checker, node) {
411
+ if (!node) return '-';
412
+ try {
413
+ const symbol = checker.getSymbolAtLocation(node);
414
+ if (symbol) {
415
+ const jsDocTags = symbol.getJsDocTags();
416
+ const descTag = jsDocTags.find(t => t.name === 'description' || t.name === 'desc');
417
+ if (descTag && descTag.text) return descTag.text;
418
+
419
+ const decls = symbol.getDeclarations();
420
+ if (decls && decls.length > 0) {
421
+ const jsDoc = ts.getJSDocCommentsAndTags(decls[0]);
422
+ if (jsDoc && jsDoc.length > 0 && typeof jsDoc[0].comment === 'string') {
423
+ return jsDoc[0].comment.trim().split('\n')[0];
424
+ }
425
+ }
426
+ }
427
+ } catch (e) { /* fall through */ }
428
+ return '-';
429
+ }
430
+
431
+ /** 获取成员签名字符串 */
432
+ function getMemberSignature(checker, node) {
433
+ try {
434
+ if (node.name) {
435
+ const symbol = checker.getSymbolAtLocation(node.name);
436
+ if (symbol) {
437
+ const type = checker.getTypeOfSymbolAtLocation(symbol, node);
438
+ if (type) {
439
+ const typeStr = checker.typeToString(type);
440
+ if (typeStr && typeStr !== 'any') {
441
+ return `${node.name.text}: ${typeStr}`;
442
+ }
443
+ }
444
+ }
445
+ }
446
+ } catch (e) { /* fall through */ }
447
+ return node.getText().trim().split('\n')[0];
448
+ }
449
+
450
+ /** 判断成员是否为 static */
451
+ function isStaticMember(node) {
452
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
453
+ if (!modifiers) return false;
454
+ return modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
455
+ }
456
+
457
+ /** 判断成员是否为 readonly */
458
+ function isReadonlyMember(node) {
459
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
460
+ if (!modifiers) return false;
461
+ return modifiers.some(m => m.kind === ts.SyntaxKind.ReadonlyKeyword);
462
+ }
463
+
464
+ /** 判断成员是否为 private/protected/internal */
465
+ function isPrivateMember(node) {
466
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
467
+ if (!modifiers) return false;
468
+ return modifiers.some(m =>
469
+ m.kind === ts.SyntaxKind.PrivateKeyword ||
470
+ m.kind === ts.SyntaxKind.ProtectedKeyword
471
+ );
472
+ }
473
+
474
+ /** 判断是否为 @Component struct 组件 */
475
+ function isStructComponent(node) {
476
+ if (!node.modifiers) return false;
477
+ return node.modifiers.some(m =>
478
+ m.kind === ts.SyntaxKind.Decorator &&
479
+ m.expression &&
480
+ ts.isIdentifier(m.expression) &&
481
+ m.expression.text === 'Component'
482
+ );
483
+ }
484
+
485
+ // ── 成员分类 ──────────────────────────────────────────────
486
+
487
+ /** 检查属性的类型是否为函数类型(如 `play: () => void`) */
488
+ function isFunctionTypedProperty(node) {
489
+ if (!node.type) return false;
490
+ const t = node.type;
491
+ // 直接函数类型节点: () => void
492
+ if (ts.isFunctionTypeNode(t)) return true;
493
+ // 联合类型中包含函数类型: (() => void) | null
494
+ if (ts.isUnionTypeNode(t)) {
495
+ return t.types.some(ty => ts.isFunctionTypeNode(ty));
496
+ }
497
+ // 括号包裹的函数类型
498
+ if (ts.isParenthesizedTypeNode(t)) {
499
+ return ts.isFunctionTypeNode(t.type);
500
+ }
501
+ return false;
502
+ }
503
+
504
+ /** 从函数类型节点提取参数列表 */
505
+ function extractParamsFromFunctionType(checker, funcTypeNode) {
506
+ const params = [];
507
+ if (!funcTypeNode.parameters) return params;
508
+ for (const param of funcTypeNode.parameters) {
509
+ if (!param.name) continue;
510
+ const name = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();
511
+ const optional = !!param.questionToken || (param.initializer !== undefined);
512
+ const typeStr = param.type ? param.type.getText() : '-';
513
+ params.push({ name, type: typeStr, optional, description: '-' });
514
+ }
515
+ return params;
516
+ }
517
+
518
+ /** 从属性节点的函数类型中提取参数和返回类型 */
519
+ function extractFuncTypeInfoFromProperty(node) {
520
+ if (!node.type) return { params: [], returnType: 'void' };
521
+ let funcType = null;
522
+ if (ts.isFunctionTypeNode(node.type)) {
523
+ funcType = node.type;
524
+ } else if (ts.isUnionTypeNode(node.type)) {
525
+ funcType = node.type.types.find(t => ts.isFunctionTypeNode(t));
526
+ } else if (ts.isParenthesizedTypeNode(node.type) && ts.isFunctionTypeNode(node.type.type)) {
527
+ funcType = node.type.type;
528
+ }
529
+ if (!funcType) return { params: [], returnType: 'void' };
530
+ const params = extractParamsFromFunctionType(null, funcType);
531
+ const returnType = funcType.type ? funcType.type.getText() : 'void';
532
+ return { params, returnType };
533
+ }
534
+
535
+ function classifyMember(node, isStruct) {
536
+ if (ts.isConstructorDeclaration(node)) {
537
+ return { memberName: 'constructor', kind: 'constructor', skip: true };
538
+ }
539
+ if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) {
540
+ if (isStruct) {
541
+ return { memberName: node.name.text, kind: 'method', skip: true };
542
+ }
543
+ if (isPrivateMember(node)) {
544
+ return { memberName: node.name.text, kind: 'method', skip: true };
545
+ }
546
+ return { memberName: node.name.text, kind: 'method', skip: false };
547
+ }
548
+ if (ts.isPropertyDeclaration(node) || ts.isPropertySignature(node) ||
549
+ ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node) ||
550
+ ts.isAccessor(node)) {
551
+ // 检测函数类型属性: play: () => void → 重分类为 method
552
+ const isFuncTyped = isFunctionTypedProperty(node);
553
+ if (isStruct) {
554
+ if (isPrivateMember(node)) {
555
+ return { memberName: node.name.text, kind: isFuncTyped ? 'method' : 'property', skip: true };
556
+ }
557
+ // struct 公有属性: 函数类型也跳过(struct 的 build 方法不导出)
558
+ return { memberName: node.name.text, kind: 'property', skip: false };
559
+ }
560
+ if (isPrivateMember(node)) {
561
+ return { memberName: node.name.text, kind: isFuncTyped ? 'method' : 'property', skip: true };
562
+ }
563
+ if (isFuncTyped) {
564
+ return { memberName: node.name.text, kind: 'method', skip: false };
565
+ }
566
+ return { memberName: node.name.text, kind: 'property', skip: false };
567
+ }
568
+ return null;
569
+ }
570
+
571
+ // ── 参数提取 ──────────────────────────────────────────────
572
+
573
+ /** 从方法节点提取参数列表 */
574
+ function extractParams(checker, node) {
575
+ const params = [];
576
+ if (!node.parameters) return params;
577
+ for (const param of node.parameters) {
578
+ if (!param.name) continue;
579
+ const name = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();
580
+ const optional = !!param.questionToken || (param.initializer !== undefined);
581
+ let typeStr = '-';
582
+ try {
583
+ const type = checker.getTypeAtLocation(param);
584
+ typeStr = checker.typeToString(type);
585
+ if (typeStr === 'any' && param.type) typeStr = param.type.getText();
586
+ } catch (e) {
587
+ if (param.type) typeStr = param.type.getText();
588
+ }
589
+ params.push({ name, type: typeStr, optional, description: '-' });
590
+ }
591
+ return params;
592
+ }
593
+
594
+ /** 从方法节点提取返回类型 */
595
+ function extractReturnType(checker, node) {
596
+ try {
597
+ if (node.type) return node.type.getText();
598
+ const sig = checker.getSignatureFromDeclaration(node);
599
+ if (sig) {
600
+ const retType = checker.getReturnTypeOfSignature(sig);
601
+ return checker.typeToString(retType);
602
+ }
603
+ } catch (e) { /* fall through */ }
604
+ return 'void';
605
+ }
606
+
607
+ /**
608
+ * 统一生成方法/函数类成员的签名,保证 signature 与 params/returnType 字段自洽。
609
+ * 形态: name: (p1?: T1, p2: T2) => ReturnType (returnType 取自 extractReturnType,保留 | null 等联合细节)
610
+ */
611
+ function buildMethodSignature(name, params, returnType, optional) {
612
+ const paramStr = (params || [])
613
+ .map(p => `${p.name}${p.optional ? '?' : ''}: ${p.type === '-' ? 'any' : p.type}`)
614
+ .join(', ');
615
+ return `${name}${optional ? '?' : ''}: (${paramStr}) => ${returnType}`;
616
+ }
617
+
618
+ // ── 类成员提取 ──────────────────────────────────────────────
619
+
620
+ function extractClassMembers(node, checker, className, isStruct, sourceFile, realPath, repoDir) {
621
+ const members = [];
622
+ for (const member of node.members) {
623
+ const classified = classifyMember(member, isStruct);
624
+ if (!classified || classified.skip) continue;
625
+
626
+ const { memberName, kind } = classified;
627
+ const isStatic = isStaticMember(member);
628
+ const isReadonly = isReadonlyMember(member);
629
+ const isEvent = /^on\w+/.test(memberName);
630
+ const access = isPrivateMember(member) ? 'private' : 'public';
631
+ const lowValue = !!(isStatic && isReadonly && kind === 'property');
632
+
633
+ // 对于函数类型属性(被重分类为 method),从函数类型节点提取参数和返回值
634
+ let params = [];
635
+ let returnType = '-';
636
+ let methodSignature = null;
637
+ if (kind === 'method') {
638
+ if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
639
+ params = extractParams(checker, member);
640
+ returnType = extractReturnType(checker, member);
641
+ methodSignature = buildMethodSignature(memberName, params, returnType, false);
642
+ } else if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
643
+ const funcInfo = extractFuncTypeInfoFromProperty(member);
644
+ params = funcInfo.params;
645
+ returnType = funcInfo.returnType;
646
+ methodSignature = buildMethodSignature(memberName, params, returnType, false);
647
+ }
648
+ }
649
+
650
+ members.push({
651
+ className,
652
+ memberName,
653
+ fullName: `${className}.${memberName}`,
654
+ kind,
655
+ isStatic,
656
+ isReadonly,
657
+ isOptional: false,
658
+ isEvent,
659
+ access,
660
+ lowValue,
661
+ type: isStruct ? 'struct' : 'class',
662
+ signature: methodSignature || getMemberSignature(checker, member),
663
+ params,
664
+ returnType,
665
+ description: getJsDocDescription(checker, member.name || member),
666
+ source: rel(realPath, repoDir),
667
+ startLine: sourceFile ? sourceFile.getLineAndCharacterOfPosition(member.getStart ? member.getStart() : member.pos).line + 1 : 0,
668
+ });
669
+ }
670
+
671
+ // 内部类展开:检测 `static X = new Y()` 公有静态属性,若同文件存在 `class Y`,
672
+ // 将 Y 的公开方法以 `X.方法名()` 前缀合并进成员
673
+ const memberDefs = members.slice();
674
+ for (const m of memberDefs) {
675
+ const im = (m.signature || '').match(/(?:^|\s)static\s+(\w+)\s*=\s*new\s+(\w+)\s*\(/i);
676
+ if (!im) continue;
677
+ const prefixName = im[1];
678
+ const innerName = im[2];
679
+ // 在 AST 中查找同名内部类
680
+ ts.forEachChild(sourceFile, (child) => {
681
+ if (ts.isClassDeclaration(child) && child.name && child.name.text === innerName) {
682
+ const innerMembers = extractClassMembers(child, checker, innerName, false, sourceFile, realPath, repoDir);
683
+ for (const inner of innerMembers) {
684
+ if (inner.name === 'constructor' || /^new\s*\(/.test(inner.signature || '')) continue;
685
+ members.push({
686
+ ...inner,
687
+ memberName: `${prefixName}.${inner.memberName}`,
688
+ fullName: `${className}.${prefixName}.${inner.memberName}`,
689
+ className,
690
+ });
691
+ }
692
+ }
693
+ });
694
+ }
695
+
696
+ return members;
697
+ }
698
+
699
+ // ── 接口字段提取 ──────────────────────────────────────────────
700
+
701
+ function extractInterfaceFields(node, checker, interfaceName, sourceFile, realPath, repoDir) {
702
+ const fields = [];
703
+ for (const member of node.members) {
704
+ if (ts.isPropertySignature(member) && member.name) {
705
+ const name = member.name.text;
706
+ const optional = !!member.questionToken;
707
+ const readonly = !!(member.modifiers && member.modifiers.some(m => m.kind === ts.SyntaxKind.ReadonlyKeyword));
708
+ const fieldType = member.type ? member.type.getText() : '-';
709
+ const isEvent = /^on\w+/.test(name);
710
+
711
+ // 检测函数类型属性: onComplete: () => void → kind=method
712
+ const isFuncTyped = isFunctionTypedProperty(member);
713
+ let params = [];
714
+ let returnType = fieldType;
715
+ let kind = 'field';
716
+ let sigStr = `${name}${optional ? '?' : ''}: ${fieldType}`;
717
+ if (isFuncTyped) {
718
+ const funcInfo = extractFuncTypeInfoFromProperty(member);
719
+ params = funcInfo.params;
720
+ returnType = funcInfo.returnType;
721
+ kind = 'method';
722
+ sigStr = buildMethodSignature(name, funcInfo.params, funcInfo.returnType, optional);
723
+ }
724
+
725
+ fields.push({
726
+ className: interfaceName,
727
+ memberName: name,
728
+ fullName: `${interfaceName}.${name}`,
729
+ kind,
730
+ isStatic: false,
731
+ isReadonly: readonly,
732
+ isOptional: optional,
733
+ isEvent,
734
+ type: 'interface',
735
+ signature: sigStr,
736
+ params,
737
+ returnType,
738
+ description: getJsDocDescription(checker, member.name),
739
+ source: rel(realPath, repoDir),
740
+ startLine: sourceFile ? sourceFile.getLineAndCharacterOfPosition(member.getStart ? member.getStart() : member.pos).line + 1 : 0,
741
+ });
742
+ } else if (ts.isMethodSignature(member) && member.name) {
743
+ const name = member.name.text;
744
+ let methodSig = null;
745
+ if (name === 'new') { /* 构造签名:保留原生形态 */ } else {
746
+ const params = extractParams(checker, member);
747
+ const returnType = extractReturnType(checker, member);
748
+ methodSig = buildMethodSignature(name, params, returnType, !!member.questionToken);
749
+ }
750
+ if (!methodSig) {
751
+ let rawSig = '-';
752
+ try {
753
+ const symbol = checker.getSymbolAtLocation(member.name);
754
+ if (symbol) {
755
+ const type = checker.getTypeOfSymbolAtLocation(symbol, member);
756
+ rawSig = checker.typeToString(type);
757
+ }
758
+ } catch (e) { /* use text */ }
759
+ if (rawSig === 'any' || rawSig === '-') {
760
+ rawSig = member.getText().trim().split('\n')[0];
761
+ }
762
+ methodSig = rawSig;
763
+ }
764
+
765
+ fields.push({
766
+ className: interfaceName,
767
+ memberName: name,
768
+ fullName: `${interfaceName}.${name}`,
769
+ kind: 'method',
770
+ isStatic: false,
771
+ isReadonly: false,
772
+ isOptional: !!member.questionToken,
773
+ isEvent: /^on\w+/.test(name),
774
+ type: 'interface',
775
+ signature: methodSig,
776
+ params: extractParams(checker, member),
777
+ returnType: extractReturnType(checker, member),
778
+ description: getJsDocDescription(checker, member.name),
779
+ source: rel(realPath, repoDir),
780
+ startLine: sourceFile ? sourceFile.getLineAndCharacterOfPosition(member.getStart ? member.getStart() : member.pos).line + 1 : 0,
781
+ });
782
+ }
783
+ }
784
+ return fields;
785
+ }
786
+
787
+ // ── 顶层导出提取 ──────────────────────────────────────────────
788
+
789
+ function extractTopLevelExports(node, checker, realPath, repoDir, sourceFile) {
790
+ const exports = [];
791
+
792
+ if (ts.isVariableStatement(node)) {
793
+ for (const decl of node.declarationList.declarations) {
794
+ if (!decl.name || !ts.isIdentifier(decl.name)) continue;
795
+ const name = decl.name.text;
796
+ let typeStr = '-';
797
+ try {
798
+ const type = checker.getTypeAtLocation(decl);
799
+ typeStr = checker.typeToString(type);
800
+ } catch (e) { /* use '-' */ }
801
+ if (typeStr === 'any' && decl.type) typeStr = decl.type.getText();
802
+
803
+ // 检测函数类型变量: lottiePlay: (lottieId?: string) => void → kind=function
804
+ const isFuncTyped = decl.type ? (ts.isFunctionTypeNode(decl.type) ||
805
+ (ts.isUnionTypeNode(decl.type) && decl.type.types.some(t => ts.isFunctionTypeNode(t))) ||
806
+ (ts.isParenthesizedTypeNode(decl.type) && ts.isFunctionTypeNode(decl.type.type))) : false;
807
+ let kind = 'variable';
808
+ let params = [];
809
+ let returnType = typeStr;
810
+ let sigStr = `${name}: ${typeStr}`;
811
+ if (isFuncTyped) {
812
+ kind = 'function';
813
+ let funcType = decl.type;
814
+ if (ts.isUnionTypeNode(funcType)) funcType = funcType.types.find(t => ts.isFunctionTypeNode(t));
815
+ if (ts.isParenthesizedTypeNode(funcType)) funcType = funcType.type;
816
+ if (funcType && ts.isFunctionTypeNode(funcType)) {
817
+ for (const param of funcType.parameters) {
818
+ if (!param.name) continue;
819
+ const pname = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();
820
+ params.push({ name: pname, type: param.type ? param.type.getText() : '-', optional: !!param.questionToken, description: '-' });
821
+ }
822
+ returnType = funcType.type ? funcType.type.getText() : 'void';
823
+ }
824
+ sigStr = buildMethodSignature(name, params, returnType, false);
825
+ }
826
+
827
+ exports.push({
828
+ className: null,
829
+ memberName: name,
830
+ fullName: name,
831
+ kind,
832
+ isStatic: false,
833
+ isReadonly: false,
834
+ isOptional: false,
835
+ isEvent: false,
836
+ type: kind === 'function' ? 'function' : 'variable',
837
+ signature: sigStr,
838
+ params,
839
+ returnType,
840
+ description: getJsDocDescription(checker, decl.name),
841
+ source: rel(realPath, repoDir),
842
+ startLine: sourceFile ? sourceFile.getLineAndCharacterOfPosition(decl.getStart ? decl.getStart() : decl.pos).line + 1 : 0,
843
+ });
844
+ }
845
+ } else if (ts.isFunctionDeclaration(node) && node.name) {
846
+ const name = node.name.text;
847
+ const params = extractParams(checker, node);
848
+ const returnType = extractReturnType(checker, node);
849
+ const sigStr = buildMethodSignature(name, params, returnType, false);
850
+
851
+ exports.push({
852
+ className: null,
853
+ memberName: name,
854
+ fullName: name,
855
+ kind: 'function',
856
+ isStatic: false,
857
+ isReadonly: false,
858
+ isOptional: false,
859
+ isEvent: false,
860
+ type: 'function',
861
+ signature: sigStr,
862
+ params,
863
+ returnType,
864
+ description: getJsDocDescription(checker, node.name),
865
+ source: rel(realPath, repoDir),
866
+ startLine: 0,
867
+ });
868
+ }
869
+ return exports;
870
+ }
871
+
872
+ // ── Enum 成员提取 ──────────────────────────────────────────────
873
+
874
+ function extractEnumMembers(node, checker, enumName, realPath, repoDir) {
875
+ const members = [];
876
+ for (const member of node.members) {
877
+ if (!member.name) continue;
878
+ const name = ts.isIdentifier(member.name) ? member.name.text : member.name.getText();
879
+ let value = '-';
880
+ try {
881
+ const type = checker.getTypeAtLocation(member);
882
+ value = checker.typeToString(type);
883
+ } catch (e) { /* use text */ }
884
+ if (value === 'any' || value === '-') {
885
+ value = member.initializer ? member.initializer.getText() : name;
886
+ }
887
+ members.push({
888
+ className: enumName,
889
+ memberName: name,
890
+ fullName: `${enumName}.${name}`,
891
+ kind: 'enumValue',
892
+ isStatic: true,
893
+ isReadonly: true,
894
+ isOptional: false,
895
+ isEvent: false,
896
+ type: 'enum',
897
+ signature: `${name} = ${value}`,
898
+ params: [],
899
+ returnType: value,
900
+ description: getJsDocDescription(checker, member.name),
901
+ source: rel(realPath, repoDir),
902
+ startLine: 0,
903
+ });
904
+ }
905
+ return members;
906
+ }
907
+
908
+ // ── declare var 静态方法提取 ───────────────────────────────────
909
+
910
+ /**
911
+ * 从 declare var body 提取静态方法(C 风格 .d.ts 类声明)
912
+ * 形如: declare var LottiePlayer: { prototype: ILottiePlayer; new(): ILottiePlayer; play(): void; ... }
913
+ */
914
+ function extractDeclareVarStatics(node, checker, varName, sourceFile, realPath, repoDir) {
915
+ const members = [];
916
+ // declare var 的类型在 node.type 中(TypeLiteral 或 IntersectionType)
917
+ if (!node.type) return members;
918
+
919
+ // 如果是 TypeLiteral,遍历其成员
920
+ let typeMembers = [];
921
+ if (ts.isTypeLiteralNode(node.type)) {
922
+ typeMembers = node.type.members;
923
+ } else if (ts.isIntersectionTypeNode(node.type)) {
924
+ for (const t of node.type.types) {
925
+ if (ts.isTypeLiteralNode(t)) {
926
+ typeMembers = typeMembers.concat(t.members);
927
+ }
928
+ }
929
+ }
930
+
931
+ for (const member of typeMembers) {
932
+ // 跳过 prototype 和 new(...) 签名
933
+ if (ts.isPropertySignature(member) && member.name) {
934
+ const name = member.name.text;
935
+ if (name === 'prototype') continue;
936
+ const isStatic = false; // declare var 的成员都是实例方法
937
+ const fieldType = member.type ? member.type.getText() : '-';
938
+
939
+ // 检测函数类型属性: play: () => void → kind=method
940
+ const isFuncTyped = isFunctionTypedProperty(member);
941
+ let params = [];
942
+ let returnType = fieldType;
943
+ let kind = 'property';
944
+ let sigStr = `${name}${member.questionToken ? '?' : ''}: ${fieldType}`;
945
+ if (isFuncTyped) {
946
+ const funcInfo = extractFuncTypeInfoFromProperty(member);
947
+ params = funcInfo.params;
948
+ returnType = funcInfo.returnType;
949
+ kind = 'method';
950
+ sigStr = buildMethodSignature(name, funcInfo.params, funcInfo.returnType, !!member.questionToken);
951
+ }
952
+
953
+ members.push({
954
+ className: varName,
955
+ memberName: name,
956
+ fullName: `${varName}.${name}`,
957
+ kind,
958
+ isStatic,
959
+ isReadonly: false,
960
+ isOptional: !!member.questionToken,
961
+ isEvent: /^on\w+/.test(name),
962
+ type: 'dts-class',
963
+ signature: sigStr,
964
+ params,
965
+ returnType,
966
+ description: getJsDocDescription(checker, member.name),
967
+ source: rel(realPath, repoDir),
968
+ startLine: sourceFile ? sourceFile.getLineAndCharacterOfPosition(member.getStart ? member.getStart() : member.pos).line + 1 : 0,
969
+ });
970
+ } else if (ts.isMethodSignature(member) && member.name) {
971
+ const name = member.name.text;
972
+ if (name === 'new') continue; // 跳过构造签名
973
+ const params = extractParams(checker, member);
974
+ const returnType = extractReturnType(checker, member);
975
+ let sig = buildMethodSignature(name, params, returnType, !!member.questionToken);
976
+ if (!sig || sig === `(${name}) => any`) {
977
+ try {
978
+ const symbol = checker.getSymbolAtLocation(member.name);
979
+ if (symbol) {
980
+ const type = checker.getTypeOfSymbolAtLocation(symbol, member);
981
+ sig = checker.typeToString(type);
982
+ }
983
+ } catch (e) { /* use text */ }
984
+ if (sig === 'any' || sig === '-') {
985
+ sig = member.getText().trim().split('\n')[0];
986
+ }
987
+ }
988
+
989
+ members.push({
990
+ className: varName,
991
+ memberName: name,
992
+ fullName: `${varName}.${name}`,
993
+ kind: 'method',
994
+ isStatic: false,
995
+ isReadonly: false,
996
+ isOptional: !!member.questionToken,
997
+ isEvent: /^on\w+/.test(name),
998
+ type: 'dts-class',
999
+ signature: sig,
1000
+ params,
1001
+ returnType,
1002
+ description: getJsDocDescription(checker, member.name),
1003
+ source: rel(realPath, repoDir),
1004
+ startLine: sourceFile ? sourceFile.getLineAndCharacterOfPosition(member.getStart ? member.getStart() : member.pos).line + 1 : 0,
1005
+ });
1006
+ }
1007
+ }
1008
+ return members;
1009
+ }
1010
+
1011
+ // ── .so 模块 Native 声明检测 ───────────────────────────────────
1012
+
1013
+ /**
1014
+ * 从 Index.ets 检测 Native .so 模块导出,返回 .d.ts 文件路径和导出名称集合
1015
+ */
1016
+ function detectNativeSo(repoDir, entryFile) {
1017
+ const indexContent = readText(entryFile);
1018
+ const indexLines = indexContent.split('\n');
1019
+
1020
+ const hasNativeSoImport = /import\s+\w+\s+from\s+['"][^'"]*\.so['"]/.test(indexContent);
1021
+ const hasNativeSoExportStar = /export\s*\*\s*from\s+['"][^'"]*\.so['"]/.test(indexContent);
1022
+ const hasNativeSoNamedExport = /export\s+(?:type\s+)?\{[^}]+\}\s*from\s+['"][^'"]*\.so['"]/.test(indexContent);
1023
+
1024
+ if (!hasNativeSoImport && !hasNativeSoExportStar && !hasNativeSoNamedExport) {
1025
+ return { nativeDPath: null, nativeSoExportNames: new Set() };
1026
+ }
1027
+
1028
+ // 收集从 .so 具名导出的名称
1029
+ const nativeSoExportNames = new Set();
1030
+ for (const l of indexLines) {
1031
+ const namedSoM = l.match(/^export\s+(?:type\s+)?\{([^}]*)\}\s*from\s*['"][^'"]*\.so['"]/);
1032
+ if (namedSoM) {
1033
+ const names = namedSoM[1].split(',').map((s) => s.trim().replace(/^.*\bas\s+/, '')).filter(Boolean);
1034
+ for (const n of names) nativeSoExportNames.add(n);
1035
+ }
1036
+ }
1037
+
1038
+ // 定位 .d.ts 文件
1039
+ let nativeDPath = null;
1040
+ for (const l of indexLines) {
1041
+ const soM = l.match(/from\s+['"]([^'"]*\.so)['"]/);
1042
+ if (soM) {
1043
+ const soName = soM[1];
1044
+ const libBase = soName.replace(/\.so$/, '').replace(/.*\//, '');
1045
+ const dtsPaths = glob(repoDir, `**/*.d.ts`);
1046
+ const detsPaths = glob(repoDir, `**/*.d.ets`);
1047
+ let nativeFile = dtsPaths.find((p) => p.toLowerCase().includes(libBase.toLowerCase()));
1048
+ if (!nativeFile) nativeFile = detsPaths.find((p) => p.toLowerCase().includes(libBase.toLowerCase()));
1049
+ if (!nativeFile) nativeFile = dtsPaths.find((p) => /libohos_|webrtc/.test(p.toLowerCase()));
1050
+ if (!nativeFile && dtsPaths.length > 0) nativeFile = dtsPaths[0];
1051
+ if (nativeFile) {
1052
+ nativeDPath = nativeFile;
1053
+ // 若命中 index.d.ts 转发层(export * from './xxx'),追踪到实际声明文件
1054
+ const nativeContent = readText(nativeFile);
1055
+ const starExp = nativeContent.match(/export\s*\*\s*from\s*['"]([^'"]+)['"]/);
1056
+ if (starExp && /index\.d\.ts$/i.test(nativeFile)) {
1057
+ const target = path.resolve(path.dirname(nativeFile), starExp[1]);
1058
+ for (const cand of [target, `${target}.d.ts`, path.join(target, 'index.d.ts')]) {
1059
+ if (fs.existsSync(cand) && fs.statSync(cand).isFile()) {
1060
+ nativeDPath = cand;
1061
+ break;
1062
+ }
1063
+ }
1064
+ }
1065
+ }
1066
+ break;
1067
+ }
1068
+ }
1069
+
1070
+ return { nativeDPath, nativeSoExportNames };
1071
+ }
1072
+
1073
+ // ── 主解析:TS Compiler API ────────────────────────────────────
1074
+
1075
+ /**
1076
+ * 核心解析函数 — 将 .ets 文件映射为虚拟 .ts 路径,
1077
+ * 因为 TS 编译器会忽略不在其支持扩展名列表中的 .ets 文件。
1078
+ */
1079
+ function parseLibraryWithTS(repoDir, entryFile, nativeSoInfo, libraryName) {
1080
+ const libRoot = repoDir;
1081
+ const sourceFiles = collectSourceFiles(repoDir, entryFile);
1082
+
1083
+ // 大小写归一化:macOS/Windows 文件系统大小写不敏感,locateEntryFile 可能返回
1084
+ // Index.ets 而磁盘实际文件名为 index.ets,导致 entryFile 不在 sourceFiles 中。
1085
+ // 用 sourceFiles 中的实际路径替换 entryFile(大小写不敏感匹配)。
1086
+ if (!sourceFiles.includes(entryFile)) {
1087
+ const lower = entryFile.toLowerCase();
1088
+ const real = sourceFiles.find((f) => f.toLowerCase() === lower);
1089
+ if (real) entryFile = real;
1090
+ }
1091
+
1092
+ // 收集 Native .d.ts 文件(可能在模块根目录外)
1093
+ if (nativeSoInfo.nativeDPath && !sourceFiles.includes(nativeSoInfo.nativeDPath)) {
1094
+ sourceFiles.push(nativeSoInfo.nativeDPath);
1095
+ }
1096
+ if (sourceFiles.length === 0) {
1097
+ console.error(`错误: 未找到源文件(入口: ${entryFile})`);
1098
+ process.exit(1);
1099
+ }
1100
+
1101
+ if (!libraryName) libraryName = extractLibraryName(repoDir, entryFile);
1102
+
1103
+ // Windows 兼容:TS 编译器内部使用正斜杠 / 作为路径分隔符,
1104
+ // 需将所有作为 fileContents/virtualPaths key 的路径统一为 POSIX 风格,
1105
+ // 否则 host.getSourceFile(fileName) 的 fileName 被 TS 标准化为正斜杠后查不到。
1106
+ const toPosix = (p) => p.replace(/\\/g, '/');
1107
+
1108
+ // 将 .ets 文件映射为虚拟 .ts 路径,.d.ts 保持原样
1109
+ const virtualPaths = new Map(); // virtualPath(POSIX) -> realPath(原始)
1110
+ const realToVirtual = new Map(); // realPath(原始) -> virtualPath(POSIX)
1111
+
1112
+ for (const realPath of sourceFiles) {
1113
+ let virtualPath;
1114
+ if (realPath.endsWith('.ets')) {
1115
+ virtualPath = realPath.replace(/\.ets$/, '.ts');
1116
+ } else {
1117
+ virtualPath = realPath;
1118
+ }
1119
+ virtualPath = toPosix(virtualPath);
1120
+ virtualPaths.set(virtualPath, realPath);
1121
+ realToVirtual.set(realPath, virtualPath);
1122
+ }
1123
+
1124
+ // 读取所有源文件内容(用虚拟 POSIX 路径作为 key)
1125
+ // 对 .ets 文件进行预处理:将 ArkTS 特有的 `struct` 关键字替换为 `class`
1126
+ const fileContents = {};
1127
+ for (const [vPath, rPath] of virtualPaths) {
1128
+ let content = fs.readFileSync(rPath, 'utf-8');
1129
+ if (rPath.endsWith('.ets')) {
1130
+ content = content.replace(/\b(struct)\s+(\w+)/g, 'class $2');
1131
+ }
1132
+ fileContents[vPath] = content;
1133
+ }
1134
+
1135
+ const compilerOptions = {
1136
+ target: ts.ScriptTarget.ES2021,
1137
+ module: ts.ModuleKind.ESNext,
1138
+ strict: false,
1139
+ skipLibCheck: true,
1140
+ noEmit: true,
1141
+ allowSyntheticDefaultImports: true,
1142
+ esModuleInterop: true,
1143
+ };
1144
+
1145
+ // 构建自定义 host
1146
+ const host = {
1147
+ getSourceFile: function(fileName, languageVersion) {
1148
+ const content = fileContents[fileName] || fileContents[toPosix(fileName)];
1149
+ if (content) {
1150
+ return ts.createSourceFile(fileName, content, languageVersion, true,
1151
+ fileName.endsWith('.d.ts') ? ts.ScriptKind.TS : ts.ScriptKind.TS);
1152
+ }
1153
+ if (!fileName.endsWith('.ets')) {
1154
+ try {
1155
+ const sysContent = ts.sys.readFile(fileName);
1156
+ if (sysContent !== undefined) {
1157
+ return ts.createSourceFile(fileName, sysContent, languageVersion, true);
1158
+ }
1159
+ } catch (e) { /* fall through */ }
1160
+ }
1161
+ return undefined;
1162
+ },
1163
+ getDefaultLibFileName: function() {
1164
+ return ts.getDefaultLibFilePath(compilerOptions);
1165
+ },
1166
+ writeFile: function() {},
1167
+ getCurrentDirectory: function() { return process.cwd(); },
1168
+ getCanonicalFileName: function(fileName) { return fileName; },
1169
+ useCaseSensitiveFileNames: function() { return true; },
1170
+ getNewLine: function() { return '\n'; },
1171
+ fileExists: function(fileName) {
1172
+ return !!fileContents[fileName] || !!fileContents[toPosix(fileName)] || ts.sys.fileExists(fileName);
1173
+ },
1174
+ readFile: function(fileName) {
1175
+ return fileContents[fileName] || fileContents[toPosix(fileName)] || ts.sys.readFile(fileName);
1176
+ },
1177
+ resolveModuleNames: function(moduleNames, containingFile) {
1178
+ const resolved = [];
1179
+ for (const moduleName of moduleNames) {
1180
+ let resolvedPath = null;
1181
+ const baseDir = path.dirname(containingFile);
1182
+
1183
+ if (moduleName.startsWith('.')) {
1184
+ const candidates = [
1185
+ path.resolve(baseDir, moduleName + '.ts'),
1186
+ path.resolve(baseDir, moduleName + '.d.ts'),
1187
+ path.resolve(baseDir, moduleName, 'Index.ts'),
1188
+ path.resolve(baseDir, moduleName, 'Index.d.ts'),
1189
+ ];
1190
+ for (const c of candidates) {
1191
+ const posixC = toPosix(c);
1192
+ if (fileContents[posixC] || ts.sys.fileExists(c)) {
1193
+ resolvedPath = posixC;
1194
+ break;
1195
+ }
1196
+ }
1197
+ }
1198
+
1199
+ // .so 模块 → 查找 types/ 目录下的 .d.ts
1200
+ if (moduleName.endsWith('.so') && !resolvedPath) {
1201
+ const soName = moduleName.replace(/\.so$/, '');
1202
+ const realContaining = virtualPaths.get(containingFile) || containingFile;
1203
+ const realBaseDir = path.dirname(realContaining);
1204
+ const typesCandidates = [
1205
+ path.resolve(realBaseDir, 'src/main/cpp/types', soName, 'Index.d.ts'),
1206
+ path.resolve(realBaseDir, 'types', soName, 'Index.d.ts'),
1207
+ path.resolve(path.dirname(libRoot), 'library', 'src/main/cpp/types', soName, 'Index.d.ts'),
1208
+ ];
1209
+ for (const sf of sourceFiles) {
1210
+ if (sf.includes(`types/${soName}/Index.d.ts`) || sf.includes(`types/${soName.replace(/-/g, '_')}/Index.d.ts`)) {
1211
+ typesCandidates.push(sf);
1212
+ }
1213
+ }
1214
+ for (const c of typesCandidates) {
1215
+ if (fs.existsSync(c) || fileContents[c]) {
1216
+ resolvedPath = c;
1217
+ break;
1218
+ }
1219
+ }
1220
+ }
1221
+
1222
+ if (resolvedPath) {
1223
+ resolved.push({ resolvedFileName: resolvedPath, extension: '.ts' });
1224
+ } else {
1225
+ resolved.push(undefined);
1226
+ }
1227
+ }
1228
+ return resolved;
1229
+ },
1230
+ };
1231
+
1232
+ const virtualFileList = Array.from(virtualPaths.keys());
1233
+ const program = ts.createProgram(virtualFileList, compilerOptions, host);
1234
+ const checker = program.getTypeChecker();
1235
+
1236
+ // 获取入口文件的虚拟路径
1237
+ const indexVirtualPath = realToVirtual.get(entryFile);
1238
+ if (!indexVirtualPath) {
1239
+ console.error(`错误: 无法加载 ${entryFile}`);
1240
+ process.exit(1);
1241
+ }
1242
+
1243
+ const indexSourceFile = program.getSourceFile(indexVirtualPath);
1244
+ if (!indexSourceFile) {
1245
+ console.error(`错误: Index.ets 未加载到编译程序中`);
1246
+ process.exit(1);
1247
+ }
1248
+
1249
+ // 获取入口模块的导出符号
1250
+ const indexSymbol = checker.getSymbolAtLocation(indexSourceFile);
1251
+ const exports = indexSymbol ? checker.getExportsOfModule(indexSymbol) : [];
1252
+ const indexExportedNames = new Set(exports.map(e => e.name));
1253
+
1254
+ // 公开 API 判据:仅「Index.ets 实际导出(getExportsOfModule 已解析 export * 转发链)」
1255
+ // 或「Index.ets 显式从 .so 再导出(export { X } from 'xxx.so')」。
1256
+ // 注意:不能把所有 .d.ts(含 cpp/types 内部 NAPI 类型声明)的导出计入,
1257
+ // 否则未导出的内部实现类(如门面类私有持有的 .so 包装类)会被误统计。
1258
+ const nativeSoExportNames = nativeSoInfo.nativeSoExportNames;
1259
+ const exportedNames = new Set([...indexExportedNames, ...nativeSoExportNames]);
1260
+ const exportedTypeNames = indexExportedNames; // 类型(类/接口/type)仅从 Index.ets 导出
1261
+
1262
+ const interfaces = [];
1263
+ const visitedClassNames = new Set();
1264
+ const sources = [];
1265
+
1266
+ // 遍历所有源文件
1267
+ for (const sourceFile of program.getSourceFiles()) {
1268
+ if (!virtualPaths.has(sourceFile.fileName)) continue;
1269
+ const realPath = virtualPaths.get(sourceFile.fileName);
1270
+ sources.push(rel(realPath, repoDir));
1271
+
1272
+ function visit(node) {
1273
+ // 类声明(含 struct 组件,已预处理为 class)
1274
+ if (ts.isClassDeclaration(node) && node.name) {
1275
+ const className = node.name.text;
1276
+ if (visitedClassNames.has(className)) return;
1277
+ if (!exportedTypeNames.has(className) && !nativeSoExportNames.has(className)) return;
1278
+ visitedClassNames.add(className);
1279
+
1280
+ const isStruct = isStructComponent(node);
1281
+ interfaces.push(...extractClassMembers(node, checker, className, isStruct, sourceFile, realPath, repoDir));
1282
+ }
1283
+
1284
+ // 接口声明
1285
+ if (ts.isInterfaceDeclaration(node) && node.name) {
1286
+ const ifaceName = node.name.text;
1287
+ if (visitedClassNames.has(ifaceName)) return;
1288
+ if (!exportedTypeNames.has(ifaceName) && !nativeSoExportNames.has(ifaceName)) return;
1289
+ visitedClassNames.add(ifaceName);
1290
+
1291
+ interfaces.push(...extractInterfaceFields(node, checker, ifaceName, sourceFile, realPath, repoDir));
1292
+ }
1293
+
1294
+ // 类型别名
1295
+ if (ts.isTypeAliasDeclaration(node) && node.name) {
1296
+ const typeName = node.name.text;
1297
+ if (visitedClassNames.has(typeName)) return;
1298
+ if (!exportedNames.has(typeName)) return;
1299
+ visitedClassNames.add(typeName);
1300
+
1301
+ const typeValue = node.type ? node.type.getText() : '-';
1302
+ interfaces.push({
1303
+ className: typeName,
1304
+ memberName: typeName,
1305
+ fullName: typeName,
1306
+ kind: 'type',
1307
+ isStatic: false,
1308
+ isReadonly: false,
1309
+ isOptional: false,
1310
+ isEvent: false,
1311
+ type: 'type',
1312
+ signature: `type ${typeName} = ${typeValue}`,
1313
+ params: [],
1314
+ returnType: typeValue,
1315
+ description: getJsDocDescription(checker, node.name),
1316
+ source: rel(realPath, repoDir),
1317
+ startLine: sourceFile ? sourceFile.getLineAndCharacterOfPosition(node.getStart ? node.getStart() : node.pos).line + 1 : 0,
1318
+ });
1319
+ }
1320
+
1321
+ // 枚举声明
1322
+ if (ts.isEnumDeclaration(node) && node.name) {
1323
+ const enumName = node.name.text;
1324
+ if (visitedClassNames.has(enumName)) return;
1325
+ if (!exportedTypeNames.has(enumName) && !nativeSoExportNames.has(enumName)) return;
1326
+ visitedClassNames.add(enumName);
1327
+
1328
+ interfaces.push(...extractEnumMembers(node, checker, enumName, realPath, repoDir));
1329
+ }
1330
+
1331
+ // declare var (C 风格 .d.ts 类声明)
1332
+ if (ts.isVariableStatement(node)) {
1333
+ // 检查是否为 declare var X: { prototype: ...; new(): ...; ... }
1334
+ for (const decl of node.declarationList.declarations) {
1335
+ if (!decl.name || !ts.isIdentifier(decl.name)) continue;
1336
+ const varName = decl.name.text;
1337
+ if (visitedClassNames.has(varName)) continue;
1338
+ // 检查是否含 prototype 属性(C 风格类声明标志)
1339
+ let hasPrototype = false;
1340
+ if (decl.type) {
1341
+ if (ts.isTypeLiteralNode(decl.type)) {
1342
+ hasPrototype = decl.type.members.some(m =>
1343
+ ts.isPropertySignature(m) && m.name && m.name.text === 'prototype'
1344
+ );
1345
+ } else if (ts.isIntersectionTypeNode(decl.type)) {
1346
+ for (const t of decl.type.types) {
1347
+ if (ts.isTypeLiteralNode(t)) {
1348
+ hasPrototype = t.members.some(m =>
1349
+ ts.isPropertySignature(m) && m.name && m.name.text === 'prototype'
1350
+ );
1351
+ if (hasPrototype) break;
1352
+ }
1353
+ }
1354
+ }
1355
+ }
1356
+ if (hasPrototype) {
1357
+ if (!exportedTypeNames.has(varName) && !nativeSoExportNames.has(varName)) continue;
1358
+ visitedClassNames.add(varName);
1359
+ interfaces.push(...extractDeclareVarStatics(decl, checker, varName, sourceFile, realPath, repoDir));
1360
+ }
1361
+ }
1362
+
1363
+ // 顶层变量/函数
1364
+ const topExports = extractTopLevelExports(node, checker, realPath, repoDir);
1365
+ for (const exp of topExports) {
1366
+ if (exportedNames.has(exp.memberName)) {
1367
+ interfaces.push(exp);
1368
+ }
1369
+ }
1370
+ }
1371
+
1372
+ // 顶层函数声明
1373
+ if (ts.isFunctionDeclaration(node) && node.name) {
1374
+ const name = node.name.text;
1375
+ if (!exportedNames.has(name)) return;
1376
+ // 避免重复
1377
+ if (interfaces.some(i => i.fullName === name)) return;
1378
+ const params = extractParams(checker, node);
1379
+ const returnType = extractReturnType(checker, node);
1380
+ const sig = buildMethodSignature(name, params, returnType, false);
1381
+
1382
+ interfaces.push({
1383
+ className: null,
1384
+ memberName: name,
1385
+ fullName: name,
1386
+ kind: 'function',
1387
+ isStatic: false,
1388
+ isReadonly: false,
1389
+ isOptional: false,
1390
+ isEvent: false,
1391
+ type: 'function',
1392
+ signature: sig,
1393
+ params,
1394
+ returnType,
1395
+ description: getJsDocDescription(checker, node.name),
1396
+ source: rel(realPath, repoDir),
1397
+ startLine: sourceFile ? sourceFile.getLineAndCharacterOfPosition(node.getStart ? node.getStart() : node.pos).line + 1 : 0,
1398
+ });
1399
+ }
1400
+
1401
+ ts.forEachChild(node, visit);
1402
+ }
1403
+
1404
+ visit(sourceFile);
1405
+ }
1406
+
1407
+ // 统计汇总
1408
+ const classCount = new Set(interfaces.map(i => i.className).filter(n => n !== null)).size;
1409
+ const methodCount = interfaces.filter(i => i.kind === 'method').length;
1410
+ const propertyCount = interfaces.filter(i => i.kind === 'property').length;
1411
+ const fieldCount = interfaces.filter(i => i.kind === 'field').length;
1412
+
1413
+ return {
1414
+ libraryName,
1415
+ entryFile: rel(entryFile, repoDir),
1416
+ nativeDPath: nativeSoInfo.nativeDPath ? rel(nativeSoInfo.nativeDPath, repoDir) : null,
1417
+ extractedAt: new Date().toISOString(),
1418
+ summary: {
1419
+ S: classCount, // 类级总数
1420
+ N: interfaces.length, // 方法/属性级总数
1421
+ M: 0, // 已覆盖数(覆盖率分析时填充)
1422
+ methods: methodCount,
1423
+ properties: propertyCount,
1424
+ fields: fieldCount,
1425
+ },
1426
+ sources,
1427
+ interfaces,
1428
+ };
1429
+ }
1430
+
1431
+ // ── 模块导出(供其他脚本调用)──────────────────────────────
1432
+
1433
+ /**
1434
+ * 从库源码目录直接生成接口规格数据(内存对象,无需写 JSON 中间件)
1435
+ * @param {string} repoDir 库源码根目录
1436
+ * @returns {object} interface-spec 数据对象
1437
+ */
1438
+ function generateSpec(repoDir) {
1439
+ if (!fs.existsSync(repoDir)) {
1440
+ throw new Error(`目录不存在: ${repoDir}`);
1441
+ }
1442
+ const entryFile = locateEntryFile(repoDir);
1443
+ if (!entryFile) {
1444
+ throw new Error(`无法定位入口文件 Index.ets: ${repoDir}`);
1445
+ }
1446
+ const nativeSoInfo = detectNativeSo(repoDir, entryFile);
1447
+ const libraryName = extractLibraryName(repoDir, entryFile);
1448
+ return parseLibraryWithTS(repoDir, entryFile, nativeSoInfo, libraryName);
1449
+ }
1450
+
1451
+ module.exports = { generateSpec, parseLibraryWithTS, locateEntryFile, detectNativeSo };
1452
+
1453
+ // ── 入口(直接运行时执行)──────────────────────────────────
1454
+
1455
+ if (require.main === module) {
1456
+ const args = parseArgs();
1457
+ const repoDir = args.repoDir || args.libroot || '';
1458
+ const outputPath = args.output || '';
1459
+
1460
+ if (!repoDir) {
1461
+ console.error('用法: node extract-interfaces.cjs --repoDir <库源码目录> --output <interface-spec.json>');
1462
+ process.exit(1);
1463
+ }
1464
+
1465
+ console.log(`库源码目录: ${repoDir}`);
1466
+
1467
+ try {
1468
+ const result = generateSpec(repoDir);
1469
+
1470
+ console.log(`解析完成: ${result.interfaces.length} 个接口`);
1471
+ console.log(` 库名: ${result.libraryName}`);
1472
+ console.log(` 入口文件: ${result.entryFile}`);
1473
+ if (result.nativeDPath) console.log(` Native 声明: ${result.nativeDPath}`);
1474
+ console.log(` 类级: ${result.summary.S} | 方法: ${result.summary.methods} | 属性: ${result.summary.properties} | 字段: ${result.summary.fields}`);
1475
+
1476
+ if (outputPath) {
1477
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
1478
+ fs.writeFileSync(outputPath, JSON.stringify(result, null, 2), 'utf-8');
1479
+ console.log(` 输出: ${outputPath}`);
1480
+ } else {
1481
+ console.log(JSON.stringify(result, null, 2));
1482
+ }
1483
+ } catch (e) {
1484
+ console.error(`错误: ${e.message}`);
1485
+ process.exit(1);
1486
+ }
1487
+ }