android2harmony 0.1.6 → 0.1.8

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 (227) hide show
  1. package/agents/api-analyzer.md +113 -0
  2. package/agents/app-action.md +122 -0
  3. package/agents/code-reviewer.md +191 -295
  4. package/agents/coder.md +377 -0
  5. package/agents/self-tester.md +8 -8
  6. package/agents/spec-designer.md +56 -0
  7. package/package.json +1 -1
  8. package/skills/a2h-app-req-gen/SKILL.md +174 -0
  9. package/skills/a2h-app-req-gen/references/spec-handoff.md +99 -0
  10. package/skills/a2h-app-req-gen/references/subagent-render.md +115 -0
  11. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/build.gradle.kts +41 -0
  12. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.jar +0 -0
  13. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.properties +8 -0
  14. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle.properties +3 -0
  15. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew +251 -0
  16. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew.bat +94 -0
  17. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/settings.gradle.kts +17 -0
  18. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawAnalysisDetector.java +1911 -0
  19. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawIssueRegistry.java +37 -0
  20. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawJson.java +146 -0
  21. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawLocation.java +96 -0
  22. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawRecordWriter.java +227 -0
  23. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry +1 -0
  24. package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/resources/dev/reqgen/analyzer/jvm/raw-record-v1.schema.json +93 -0
  25. package/skills/a2h-app-req-gen/scripts/reqgen/assets/dashboard.html +151 -0
  26. package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-lint.init.gradle +60 -0
  27. package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-model.init.gradle +204 -0
  28. package/skills/a2h-app-req-gen/scripts/reqgen/package.json +14 -0
  29. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/backend.js +323 -0
  30. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/cli.js +811 -0
  31. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/gap-audit-link.js +62 -0
  32. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/index.js +66 -0
  33. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/merge.js +605 -0
  34. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/name-trace.js +153 -0
  35. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/orchestrate.js +1324 -0
  36. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/output-parse.js +50 -0
  37. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/overview.js +10 -0
  38. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/pool.js +23 -0
  39. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/post-merge-fix.js +146 -0
  40. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt-loader.js +51 -0
  41. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt.js +967 -0
  42. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompts.md +301 -0
  43. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/serve.js +78 -0
  44. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/short-id.js +46 -0
  45. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/snapshot.js +267 -0
  46. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/types.js +1 -0
  47. package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/validate.js +122 -0
  48. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/callback-call-sites.js +62 -0
  49. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/build.js +1291 -0
  50. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/index.js +1 -0
  51. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/area-ownership.js +392 -0
  52. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/build.js +1995 -0
  53. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/callback-flow.js +277 -0
  54. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/component-variants.js +725 -0
  55. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/index.js +15 -0
  56. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/index.js +12 -0
  57. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/policy.js +469 -0
  58. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/traceability.js +468 -0
  59. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/build.js +1066 -0
  60. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/index.js +1 -0
  61. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android-interaction-registry.js +174 -0
  62. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android.js +1062 -0
  63. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/area-titles.js +678 -0
  64. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/builder.js +350 -0
  65. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-api-registry.js +395 -0
  66. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-call-sites.js +2008 -0
  67. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose.js +9059 -0
  68. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/dynamic-repeated-call-sites.js +529 -0
  69. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/index.js +1 -0
  70. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/interaction-effects.js +1149 -0
  71. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/jvm.js +6272 -0
  72. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/normalize.js +33 -0
  73. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/pending-intents.js +565 -0
  74. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/resource-id.js +42 -0
  75. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/route-state-variants.js +781 -0
  76. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/state.js +234 -0
  77. package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/types.js +1 -0
  78. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/extract.js +40 -0
  79. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/index.js +6 -0
  80. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/manifest.js +513 -0
  81. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/resources.js +398 -0
  82. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/types.js +1 -0
  83. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/views.js +447 -0
  84. package/skills/a2h-app-req-gen/scripts/reqgen/src/android/xml-parser.js +175 -0
  85. package/skills/a2h-app-req-gen/scripts/reqgen/src/cli.js +398 -0
  86. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/area-graph.js +1 -0
  87. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/artifact.js +1 -0
  88. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/common.js +16 -0
  89. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/diagnostic.js +10 -0
  90. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/gap-audit.js +14 -0
  91. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/index.js +11 -0
  92. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/name-traceability.js +26 -0
  93. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/raw-fact.js +40 -0
  94. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/req-model.js +1 -0
  95. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/run.js +19 -0
  96. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/semantic-completeness.js +11 -0
  97. package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/source-facts.js +1 -0
  98. package/skills/a2h-app-req-gen/scripts/reqgen/src/core/output-guard.js +41 -0
  99. package/skills/a2h-app-req-gen/scripts/reqgen/src/core/stable-id.js +92 -0
  100. package/skills/a2h-app-req-gen/scripts/reqgen/src/dashboard-cli.js +169 -0
  101. package/skills/a2h-app-req-gen/scripts/reqgen/src/diagnostics.js +104 -0
  102. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/dump.js +1028 -0
  103. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/index.js +1 -0
  104. package/skills/a2h-app-req-gen/scripts/reqgen/src/dump-cli.js +251 -0
  105. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/artifacts.js +49 -0
  106. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/index.js +18 -0
  107. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/quality-report.js +125 -0
  108. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/render.js +353 -0
  109. package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/types.js +2 -0
  110. package/skills/a2h-app-req-gen/scripts/reqgen/src/graph/paths.js +474 -0
  111. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/index.js +15 -0
  112. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/protocol.js +408 -0
  113. package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/runner.js +452 -0
  114. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/index.js +8 -0
  115. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/output.js +165 -0
  116. package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/run-stage1.js +891 -0
  117. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/discovery.js +299 -0
  118. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-model.js +358 -0
  119. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-runner.js +165 -0
  120. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/resource-profile.js +60 -0
  121. package/skills/a2h-app-req-gen/scripts/reqgen/src/project/types.js +1 -0
  122. package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff-cli.js +151 -0
  123. package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff.js +1004 -0
  124. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-cli.js +109 -0
  125. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-input.js +419 -0
  126. package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-name-trace-input.js +95 -0
  127. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/area-graph.js +444 -0
  128. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/index.js +7 -0
  129. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-model.js +580 -0
  130. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-text.js +195 -0
  131. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/semantic-area-graph.js +1431 -0
  132. package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/types.js +1 -0
  133. package/skills/a2h-app-req-gen/scripts/reqgen-audit.mjs +298 -0
  134. package/skills/a2h-app-req-gen/scripts/reqgen-selfcheck.mjs +555 -0
  135. package/skills/a2h-code-review/SKILL.md +379 -0
  136. package/skills/{hmos-integration-test → a2h-integration-test}/README.md +6 -6
  137. package/skills/{hmos-integration-test → a2h-integration-test}/SKILL.md +6 -6
  138. package/skills/{hmos-integration-test → a2h-integration-test}/scripts/report-tool.mjs +1 -1
  139. package/skills/a2h-spec-design/SKILL.md +330 -0
  140. package/skills/a2h-spec-design/scripts/inspect_spec_design_inputs.mjs +207 -0
  141. package/skills/a2h-spec-design/scripts/move_spec_technical_reference.mjs +198 -0
  142. package/skills/a2h-spec-design/scripts/validate_spec_design_output.mjs +256 -0
  143. package/skills/a2h-spec-generate/SKILL.md +696 -0
  144. package/skills/a2h-spec-generate/references/trace-template.md +115 -0
  145. package/skills/a2h-spec-generate/scripts/parse_requirements.mjs +537 -0
  146. package/skills/a2h-spec-generate/template/REQ.txt +22 -0
  147. package/skills/a2h-spec-generate/template/REQ.xlsx +0 -0
  148. package/skills/a2h-spec-implement/SKILL.md +548 -0
  149. package/skills/a2h-spec-implement/references/harmony-pitfalls.md +186 -0
  150. package/skills/a2h-spec-implement/references/l1-unit-test.md +185 -0
  151. package/skills/a2h-spec-implement/references/l3-e2e-uitest.md +167 -0
  152. package/skills/a2h-spec-implement/references/test-core.md +132 -0
  153. package/skills/a2h-spec-implement/rules/arkts/arkts-standards.md +184 -0
  154. package/skills/a2h-spec-implement/rules/arkts/conventions/coding-style.md +96 -0
  155. package/skills/a2h-spec-implement/rules/arkts/conventions/security.md +126 -0
  156. package/skills/a2h-spec-implement/rules/arkts/language/arkts-rules.md +650 -0
  157. package/skills/a2h-spec-implement/rules/arkts/language/arkui-structure-rules.md +173 -0
  158. package/skills/a2h-spec-implement/rules/arkts/language/ts-to-arkts-rewrites.md +58 -0
  159. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  160. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  161. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  162. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  163. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  164. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  165. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  166. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  167. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  168. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  169. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  170. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  171. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v1//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  172. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md +216 -0
  173. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md +442 -0
  174. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md +169 -0
  175. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md +763 -0
  176. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md +2088 -0
  177. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md +1258 -0
  178. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Once/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md +175 -0
  179. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Param/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md +850 -0
  180. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md +862 -0
  181. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md +110 -0
  182. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md +301 -0
  183. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md +1411 -0
  184. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +1392 -0
  185. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/getTarget/346/216/245/345/217/243/357/274/232/350/216/267/345/217/226/347/212/266/346/200/201/347/256/241/347/220/206/346/241/206/346/236/266/344/273/243/347/220/206/345/211/215/347/232/204/345/216/237/345/247/213/345/257/271/350/261/241.md +288 -0
  186. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2/makeObserved/346/216/245/345/217/243/357/274/232/345/260/206/351/235/236/350/247/202/345/257/237/346/225/260/346/215/256/345/217/230/344/270/272/345/217/257/350/247/202/345/257/237/346/225/260/346/215/256.md +768 -0
  187. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/222/214V2/346/267/267/347/224/250/346/214/207/345/257/274/357/274/210API version 19/345/217/212/344/271/213/345/220/216/357/274/211.md" +829 -0
  188. package/skills/a2h-spec-implement/rules/arkts/official/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206/346/246/202/350/277/260.md +184 -0
  189. package/skills/a2h-spec-implement/rules/arkts/ui/component-cookbook.md +431 -0
  190. package/skills/a2h-spec-implement/rules/arkts/ui/state-management.md +152 -0
  191. package/skills/a2h-spec-implement/rules/arkts/ui/ui-quality.md +67 -0
  192. package/skills/a2h-spec-implement/rules/arkts/ui/ui-runtime-diagnosis.md +32 -0
  193. package/skills/a2h-spec-implement/rules/pipeline/build/package-set-collection.md +43 -0
  194. package/skills/a2h-spec-implement/scripts/_lib/common.mjs +97 -0
  195. package/skills/a2h-spec-implement/scripts/_lib/config.mjs +70 -0
  196. package/skills/a2h-spec-implement/scripts/_lib/lessons_cli.mjs +48 -0
  197. package/skills/a2h-spec-implement/scripts/build_lessons.mjs +24 -0
  198. package/skills/a2h-spec-implement/scripts/code_pattern_lessons.mjs +26 -0
  199. package/skills/a2h-spec-implement/scripts/device_ui.mjs +433 -0
  200. package/skills/a2h-spec-implement/scripts/device_ui_core.mjs +895 -0
  201. package/skills/a2h-spec-implement/scripts/ledger/aggregate_run.mjs +207 -0
  202. package/skills/a2h-spec-implement/scripts/manifest/emit_summary_line.mjs +37 -0
  203. package/skills/a2h-spec-implement/scripts/manifest/finalize_run.mjs +115 -0
  204. package/skills/a2h-spec-implement/scripts/manifest/render_final_summary.mjs +78 -0
  205. package/skills/a2h-spec-implement/scripts/manifest/root_mirror.mjs +137 -0
  206. package/skills/a2h-spec-implement/scripts/package.json +12 -0
  207. package/skills/a2h-spec-implement/scripts/state_propagation_check.mjs +676 -0
  208. package/skills/a2h-spec-implement/scripts/test/l2_run.mjs +244 -0
  209. package/skills/a2h-spec-implement/scripts/test/tp_coverage.mjs +231 -0
  210. package/skills/a2h-spec-implement/scripts/ui_memory.mjs +161 -0
  211. package/skills/a2h-spec-implement/scripts/utils/ensure_app.mjs +134 -0
  212. package/skills/a2h-ui-transfer/SKILL.md +20 -9
  213. package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +80 -0
  214. package/skills/hmos-convert-pipeline/SKILL.md +7 -5
  215. package/skills/hmos-incremental-ui-align/README.md +7 -7
  216. package/skills/hmos-incremental-ui-align/SKILL.md +1 -1
  217. package/skills/hmos-incremental-ui-align/page_align.md +1 -1
  218. package/skills/hmos-spec-generate/SKILL.md +2 -2
  219. package/skills/hmos-spec-generate/scripts/parse_requirements.mjs +537 -0
  220. package/skills/hmos-fix-build-errors/SKILL.md +0 -266
  221. package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +0 -219
  222. package/skills/hmos-fix-build-errors/references/known-patterns.md +0 -157
  223. package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +0 -131
  224. package/skills/hmos-spec-generate/scripts/parse_requirements.ts +0 -515
  225. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/resolve-metadata-tool.mjs +0 -0
  226. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/self-test-runner.mjs +0 -0
  227. /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/testcases-tool.mjs +0 -0
@@ -0,0 +1,1911 @@
1
+ package dev.reqgen.analyzer.jvm;
2
+
3
+ import com.android.resources.ResourceFolderType;
4
+ import com.android.tools.lint.client.api.UElementHandler;
5
+ import com.android.tools.lint.detector.api.Category;
6
+ import com.android.tools.lint.detector.api.Context;
7
+ import com.android.tools.lint.detector.api.Detector;
8
+ import com.android.tools.lint.detector.api.Implementation;
9
+ import com.android.tools.lint.detector.api.Issue;
10
+ import com.android.tools.lint.detector.api.JavaContext;
11
+ import com.android.tools.lint.detector.api.Scope;
12
+ import com.android.tools.lint.detector.api.Severity;
13
+ import com.android.tools.lint.detector.api.SourceCodeScanner;
14
+ import com.android.tools.lint.detector.api.XmlContext;
15
+ import com.android.tools.lint.detector.api.XmlScanner;
16
+ import com.intellij.psi.PsiClass;
17
+ import com.intellij.psi.PsiCompiledFile;
18
+ import com.intellij.psi.PsiElement;
19
+ import com.intellij.psi.PsiField;
20
+ import com.intellij.psi.PsiMethod;
21
+ import com.intellij.psi.PsiModifier;
22
+ import com.intellij.psi.PsiNamedElement;
23
+ import com.intellij.psi.PsiParameter;
24
+ import com.intellij.psi.PsiType;
25
+ import com.intellij.psi.PsiVariable;
26
+ import java.util.ArrayList;
27
+ import java.util.Collection;
28
+ import java.util.EnumSet;
29
+ import java.util.IdentityHashMap;
30
+ import java.util.LinkedHashSet;
31
+ import java.util.List;
32
+ import java.util.Map;
33
+ import java.util.Set;
34
+ import org.jetbrains.uast.UAnnotated;
35
+ import org.jetbrains.uast.UArrayAccessExpression;
36
+ import org.jetbrains.uast.UBinaryExpression;
37
+ import org.jetbrains.uast.UBlockExpression;
38
+ import org.jetbrains.uast.UCallExpression;
39
+ import org.jetbrains.uast.UCallableReferenceExpression;
40
+ import org.jetbrains.uast.UClass;
41
+ import org.jetbrains.uast.UClassLiteralExpression;
42
+ import org.jetbrains.uast.UElement;
43
+ import org.jetbrains.uast.UExpression;
44
+ import org.jetbrains.uast.UFile;
45
+ import org.jetbrains.uast.UIfExpression;
46
+ import org.jetbrains.uast.ULambdaExpression;
47
+ import org.jetbrains.uast.ULiteralExpression;
48
+ import org.jetbrains.uast.UMethod;
49
+ import org.jetbrains.uast.UNamedExpression;
50
+ import org.jetbrains.uast.UObjectLiteralExpression;
51
+ import org.jetbrains.uast.UParameter;
52
+ import org.jetbrains.uast.UPolyadicExpression;
53
+ import org.jetbrains.uast.UQualifiedReferenceExpression;
54
+ import org.jetbrains.uast.UResolvable;
55
+ import org.jetbrains.uast.UReturnExpression;
56
+ import org.jetbrains.uast.USwitchClauseExpression;
57
+ import org.jetbrains.uast.USwitchExpression;
58
+ import org.jetbrains.uast.UThrowExpression;
59
+ import org.jetbrains.uast.UVariable;
60
+ import org.jetbrains.uast.UastBinaryOperator;
61
+ import org.jetbrains.uast.UastContextKt;
62
+ import org.jetbrains.uast.UastEmptyExpression;
63
+ import org.jetbrains.kotlin.asJava.elements.KtLightElement;
64
+ import org.jetbrains.kotlin.psi.KtExpression;
65
+ import org.jetbrains.kotlin.psi.KtProperty;
66
+ import org.w3c.dom.Attr;
67
+ import org.w3c.dom.Element;
68
+ import org.w3c.dom.Node;
69
+ import org.jetbrains.annotations.NotNull;
70
+ import org.jetbrains.annotations.Nullable;
71
+
72
+ public final class RawAnalysisDetector extends Detector
73
+ implements SourceCodeScanner, XmlScanner {
74
+
75
+ private static final Implementation IMPLEMENTATION = new Implementation(
76
+ RawAnalysisDetector.class,
77
+ EnumSet.of(Scope.MANIFEST, Scope.RESOURCE_FILE, Scope.JAVA_FILE),
78
+ Scope.MANIFEST_SCOPE,
79
+ Scope.RESOURCE_FILE_SCOPE,
80
+ Scope.JAVA_FILE_SCOPE
81
+ );
82
+
83
+ public static final Issue ISSUE = Issue.create(
84
+ "ReqgenRawJvmFacts",
85
+ "Collect raw Java, Kotlin, and XML facts",
86
+ "Internal, non-diagnostic collector for the mobile-app requirement "
87
+ + "generation pipeline.",
88
+ Category.CORRECTNESS,
89
+ 1,
90
+ Severity.WARNING,
91
+ IMPLEMENTATION
92
+ );
93
+
94
+ private RawRecordWriter writer;
95
+
96
+ @Override
97
+ public void beforeCheckRootProject(@NotNull Context context) {
98
+ writer = RawRecordWriter.open(context);
99
+ }
100
+
101
+ @Override
102
+ public void afterCheckRootProject(@NotNull Context context) {
103
+ if (writer != null) {
104
+ writer.close();
105
+ writer = null;
106
+ }
107
+ }
108
+
109
+ @NotNull
110
+ @Override
111
+ public List<Class<? extends UElement>> getApplicableUastTypes() {
112
+ return List.of(
113
+ UFile.class,
114
+ UClass.class,
115
+ UMethod.class,
116
+ ULambdaExpression.class,
117
+ UCallableReferenceExpression.class,
118
+ UCallExpression.class,
119
+ UBinaryExpression.class,
120
+ UIfExpression.class,
121
+ USwitchExpression.class,
122
+ UReturnExpression.class
123
+ );
124
+ }
125
+
126
+ @Nullable
127
+ @Override
128
+ public UElementHandler createUastHandler(@NotNull JavaContext context) {
129
+ if (writer == null || !writer.isEnabled()) {
130
+ return null;
131
+ }
132
+ return new SourceHandler(context);
133
+ }
134
+
135
+ @NotNull
136
+ @Override
137
+ public Collection<String> getApplicableElements() {
138
+ return XmlScanner.ALL;
139
+ }
140
+
141
+ @NotNull
142
+ @Override
143
+ public Collection<String> getApplicableAttributes() {
144
+ return XmlScanner.ALL;
145
+ }
146
+
147
+ @Override
148
+ public void visitElement(
149
+ @NotNull XmlContext context,
150
+ @NotNull Element element
151
+ ) {
152
+ if (writer == null || !writer.isEnabled()) {
153
+ return;
154
+ }
155
+ emitXmlSourceFile(context);
156
+ RawLocation location = RawLocation.from(
157
+ context,
158
+ context.getLocation(element)
159
+ );
160
+ Node parent = element.getParentNode();
161
+ writer.emit(
162
+ "xml-element",
163
+ location,
164
+ element.getTagName(),
165
+ RawJson.object(
166
+ "tagName", element.getTagName(),
167
+ "localName", valueOrEmpty(element.getLocalName()),
168
+ "namespaceUri", valueOrEmpty(element.getNamespaceURI()),
169
+ "parentTagName",
170
+ parent instanceof Element parentElement
171
+ ? parentElement.getTagName()
172
+ : "",
173
+ "resourceFolderType", resourceFolder(context)
174
+ )
175
+ );
176
+ }
177
+
178
+ @Override
179
+ public void visitAttribute(
180
+ @NotNull XmlContext context,
181
+ @NotNull Attr attribute
182
+ ) {
183
+ if (writer == null || !writer.isEnabled()) {
184
+ return;
185
+ }
186
+ emitXmlSourceFile(context);
187
+ RawLocation location = RawLocation.from(
188
+ context,
189
+ context.getLocation(attribute)
190
+ );
191
+ writer.emit(
192
+ "xml-attribute",
193
+ location,
194
+ attribute.getOwnerElement().getTagName()
195
+ + ":"
196
+ + attribute.getName(),
197
+ RawJson.object(
198
+ "ownerTagName", attribute.getOwnerElement().getTagName(),
199
+ "qualifiedName", attribute.getName(),
200
+ "localName", valueOrEmpty(attribute.getLocalName()),
201
+ "namespaceUri", valueOrEmpty(attribute.getNamespaceURI()),
202
+ "value", attribute.getValue(),
203
+ "resourceFolderType", resourceFolder(context)
204
+ )
205
+ );
206
+ }
207
+
208
+ private void emitXmlSourceFile(XmlContext context) {
209
+ writer.emitSourceFileOnce(
210
+ context,
211
+ "xml",
212
+ RawJson.object(
213
+ "xmlKind",
214
+ context.file.getName().equals("AndroidManifest.xml")
215
+ ? "manifest"
216
+ : "resource",
217
+ "resourceFolderType", resourceFolder(context)
218
+ )
219
+ );
220
+ }
221
+
222
+ private final class SourceHandler extends UElementHandler {
223
+ private final JavaContext context;
224
+ private final Map<PsiElement, String> variableDeclarationRefs =
225
+ new IdentityHashMap<>();
226
+
227
+ private SourceHandler(JavaContext context) {
228
+ this.context = context;
229
+ }
230
+
231
+ @Override
232
+ public void visitFile(@NotNull UFile node) {
233
+ writer.emitSourceFileOnce(
234
+ context,
235
+ sourceLanguage(context),
236
+ RawJson.object("packageName", node.getPackageName())
237
+ );
238
+ }
239
+
240
+ @Override
241
+ public void visitClass(@NotNull UClass node) {
242
+ RawLocation location = location(node);
243
+ List<Object> superTypes = new ArrayList<>();
244
+ for (PsiClass superClass : node.getSupers()) {
245
+ superTypes.add(valueOrEmpty(superClass.getQualifiedName()));
246
+ }
247
+ String qualifiedName = valueOrEmpty(node.getQualifiedName());
248
+ Map<String, Object> data = RawJson.object(
249
+ "language", sourceLanguage(context),
250
+ "name", valueOrEmpty(node.getName()),
251
+ "qualifiedName", qualifiedName,
252
+ "declarationKind", classKind(node),
253
+ "abstract",
254
+ node.hasModifierProperty(PsiModifier.ABSTRACT),
255
+ "final", node.hasModifierProperty(PsiModifier.FINAL),
256
+ "superTypes", superTypes,
257
+ "annotations", annotationNames(node),
258
+ "composable", isComposable(node)
259
+ );
260
+ putCallableScope(data, node);
261
+ writer.emit(
262
+ "class-declaration",
263
+ location,
264
+ classDiscriminator(node),
265
+ data
266
+ );
267
+ }
268
+
269
+ @Override
270
+ public void visitMethod(@NotNull UMethod node) {
271
+ RawLocation location = location(node);
272
+ PsiClass owner = node.getContainingClass();
273
+ List<Object> parameters = new ArrayList<>();
274
+ for (PsiParameter parameter
275
+ : node.getParameterList().getParameters()) {
276
+ parameters.add(
277
+ RawJson.object(
278
+ "name", valueOrEmpty(parameter.getName()),
279
+ "type", typeName(parameter.getType()),
280
+ "varArgs", parameter.isVarArgs()
281
+ )
282
+ );
283
+ }
284
+ String ownerName = owner == null
285
+ ? ""
286
+ : valueOrEmpty(owner.getQualifiedName());
287
+ Set<String> superMethodSignatures = new LinkedHashSet<>();
288
+ for (PsiMethod superMethod : node.findSuperMethods()) {
289
+ superMethodSignatures.add(methodSignature(superMethod));
290
+ }
291
+ List<Object> sortedSuperMethodSignatures =
292
+ new ArrayList<>(superMethodSignatures);
293
+ sortedSuperMethodSignatures.sort((left, right) ->
294
+ String.valueOf(left).compareTo(String.valueOf(right))
295
+ );
296
+ Map<String, Object> data = RawJson.object(
297
+ "language", sourceLanguage(context),
298
+ "name", node.getName(),
299
+ "ownerQualifiedName", ownerName,
300
+ "declarationKind",
301
+ node.isConstructor() ? "constructor" : "method",
302
+ "returnType", typeName(node.getReturnType()),
303
+ "parameters", parameters,
304
+ "signature", methodSignature(node),
305
+ "abstract",
306
+ node.hasModifierProperty(PsiModifier.ABSTRACT),
307
+ "superMethodSignatures", sortedSuperMethodSignatures,
308
+ "annotations", annotationNames(node),
309
+ "composable", isComposable(node)
310
+ );
311
+ UClass ownerDeclaration = enclosingClassDeclaration(node);
312
+ if (ownerDeclaration != null) {
313
+ data.put(
314
+ "ownerClassRef",
315
+ classDeclarationRef(ownerDeclaration)
316
+ );
317
+ }
318
+ writer.emit(
319
+ "method-declaration",
320
+ location,
321
+ methodSignature(node),
322
+ data
323
+ );
324
+ }
325
+
326
+ @Override
327
+ public void visitLambdaExpression(@NotNull ULambdaExpression node) {
328
+ List<Object> parameters = new ArrayList<>();
329
+ for (UParameter parameter : node.getValueParameters()) {
330
+ parameters.add(
331
+ RawJson.object(
332
+ "name", valueOrEmpty(parameter.getName()),
333
+ "type", typeName(parameter.getType())
334
+ )
335
+ );
336
+ }
337
+ Map<String, Object> data = RawJson.object(
338
+ "language", sourceLanguage(context),
339
+ "source", node.asSourceString(),
340
+ "functionalInterfaceType",
341
+ typeName(node.getFunctionalInterfaceType()),
342
+ "parameters", parameters,
343
+ "enclosingClass", enclosingClassName(node),
344
+ "enclosingMethod", enclosingMethodSignature(node)
345
+ );
346
+ putCallableScope(data, node);
347
+ writer.emit(
348
+ "lambda-declaration",
349
+ location(node),
350
+ lambdaDiscriminator(node),
351
+ data
352
+ );
353
+ }
354
+
355
+ @Override
356
+ public void visitCallableReferenceExpression(
357
+ @NotNull UCallableReferenceExpression node
358
+ ) {
359
+ PsiElement target = node.resolve();
360
+ UExpression qualifier = node.getQualifierExpression();
361
+ Map<String, Object> data = RawJson.object(
362
+ "language", sourceLanguage(context),
363
+ "source", node.asSourceString(),
364
+ "callableName", node.getCallableName(),
365
+ "qualifierSource", sourceOrEmpty(qualifier),
366
+ "qualifierType", typeName(node.getQualifierType()),
367
+ "resolved", target != null
368
+ );
369
+ if (target != null) {
370
+ data.put("target", symbolData(target));
371
+ }
372
+ putCallableScope(data, node);
373
+ writer.emit(
374
+ "callable-reference",
375
+ location(node),
376
+ callableReferenceDiscriminator(node, target),
377
+ data
378
+ );
379
+ }
380
+
381
+ @Override
382
+ public void visitCallExpression(@NotNull UCallExpression node) {
383
+ PsiMethod target = node.resolve();
384
+ String kind = target == null
385
+ ? "unresolved-call"
386
+ : "resolved-call";
387
+ Map<String, Object> data = callData(node, target);
388
+ String callId = writer.emit(
389
+ kind,
390
+ location(node),
391
+ callDiscriminator(node, target),
392
+ data
393
+ );
394
+ if (target == null) {
395
+ emitUnresolvedArgumentBindings(node, callId);
396
+ } else {
397
+ emitArgumentBindings(node, target, callId);
398
+ }
399
+ }
400
+
401
+ @Override
402
+ public void visitBinaryExpression(@NotNull UBinaryExpression node) {
403
+ String operator = node.getOperator().getText();
404
+ if (!isAssignmentOperator(operator)) {
405
+ return;
406
+ }
407
+ Map<String, Object> data = numericArrayAssignmentData(
408
+ node,
409
+ operator
410
+ );
411
+ if (data == null) {
412
+ data = RawJson.object(
413
+ "language", sourceLanguage(context),
414
+ "operator", operator,
415
+ "left", expressionData(node.getLeftOperand()),
416
+ "right", expressionData(node.getRightOperand()),
417
+ "enclosingClass", enclosingClassName(node),
418
+ "enclosingMethod", enclosingMethodSignature(node),
419
+ "conditionRefs", enclosingConditionRefs(node),
420
+ "conditionContexts", enclosingConditionContexts(node)
421
+ );
422
+ }
423
+ putCallableScope(data, node);
424
+ writer.emit(
425
+ "assignment",
426
+ location(node),
427
+ operator + ":" + node.asSourceString(),
428
+ data
429
+ );
430
+ }
431
+
432
+ /**
433
+ * Numeric lookup-table initialization can contain thousands of array
434
+ * element assignments. Resolving every array access is expensive, and
435
+ * an array element has no stable scalar symbol consumed by the current
436
+ * data-flow analysis. Keep the assignment and its literal value while
437
+ * deliberately avoiding recursive resolution of either expression.
438
+ */
439
+ @Nullable
440
+ private Map<String, Object> numericArrayAssignmentData(
441
+ UBinaryExpression node,
442
+ String operator
443
+ ) {
444
+ if (!"=".equals(operator)) {
445
+ return null;
446
+ }
447
+ UExpression left = node.getLeftOperand();
448
+ UExpression right = node.getRightOperand();
449
+ if (
450
+ !(left instanceof UArrayAccessExpression) ||
451
+ !(right instanceof ULiteralExpression literal)
452
+ ) {
453
+ return null;
454
+ }
455
+ Object value = literal.getValue();
456
+ if (
457
+ !(value instanceof Number) ||
458
+ RawJson.isNonFiniteNumber(value)
459
+ ) {
460
+ return null;
461
+ }
462
+ return RawJson.object(
463
+ "language", sourceLanguage(context),
464
+ "operator", operator,
465
+ "left", RawJson.object(
466
+ "kind", "array-access",
467
+ "source", left.asSourceString(),
468
+ "expressionType", typeName(left.getExpressionType())
469
+ ),
470
+ "right", RawJson.object(
471
+ "kind", "literal",
472
+ "source", right.asSourceString(),
473
+ "expressionType", typeName(right.getExpressionType()),
474
+ "constantValue", value
475
+ ),
476
+ "enclosingClass", enclosingClassName(node),
477
+ "enclosingMethod", enclosingMethodSignature(node),
478
+ "conditionRefs", enclosingConditionRefs(node),
479
+ "conditionContexts", enclosingConditionContexts(node)
480
+ );
481
+ }
482
+
483
+ @Override
484
+ public void visitIfExpression(@NotNull UIfExpression node) {
485
+ emitCondition(
486
+ node,
487
+ node.getCondition(),
488
+ "if",
489
+ node.getCondition().asSourceString()
490
+ );
491
+ }
492
+
493
+ @Override
494
+ public void visitSwitchExpression(@NotNull USwitchExpression node) {
495
+ UExpression subject = node.getExpression();
496
+ emitCondition(
497
+ node,
498
+ subject,
499
+ "switch-or-when",
500
+ subject == null ? node.asSourceString() : subject.asSourceString()
501
+ );
502
+ }
503
+
504
+ @Override
505
+ public void visitReturnExpression(@NotNull UReturnExpression node) {
506
+ UExpression value = node.getReturnExpression();
507
+ Map<String, Object> data = RawJson.object(
508
+ "language", sourceLanguage(context),
509
+ "source", node.asSourceString(),
510
+ "enclosingClass", enclosingClassName(node),
511
+ "enclosingMethod", enclosingMethodSignature(node),
512
+ "conditionRefs", enclosingConditionRefs(node),
513
+ "conditionContexts", enclosingConditionContexts(node)
514
+ );
515
+ if (value != null) {
516
+ data.put("expression", expressionData(value));
517
+ }
518
+ putCallableScope(data, node);
519
+ writer.emit(
520
+ "return",
521
+ location(node),
522
+ node.asSourceString(),
523
+ data
524
+ );
525
+ }
526
+
527
+ private void emitArgumentBindings(
528
+ UCallExpression call,
529
+ PsiMethod target,
530
+ String callId
531
+ ) {
532
+ Map<UExpression, PsiParameter> mapping =
533
+ context.getEvaluator().computeArgumentMapping(call, target);
534
+ Set<UExpression> arguments = java.util.Collections.newSetFromMap(
535
+ new IdentityHashMap<>()
536
+ );
537
+ List<UExpression> orderedArguments = new ArrayList<>();
538
+ for (UExpression argument : call.getValueArguments()) {
539
+ if (arguments.add(argument)) {
540
+ orderedArguments.add(argument);
541
+ }
542
+ }
543
+ for (UExpression argument : mapping.keySet()) {
544
+ if (
545
+ arguments.add(argument) &&
546
+ orderedArguments.stream().noneMatch(existing ->
547
+ sameSource(existing, argument)
548
+ )
549
+ ) {
550
+ orderedArguments.add(argument);
551
+ }
552
+ }
553
+
554
+ PsiParameter[] parameters =
555
+ target.getParameterList().getParameters();
556
+ for (int argumentIndex = 0;
557
+ argumentIndex < orderedArguments.size();
558
+ argumentIndex += 1) {
559
+ UExpression argument = orderedArguments.get(argumentIndex);
560
+ PsiParameter parameter = mappedParameter(
561
+ call,
562
+ argument,
563
+ mapping,
564
+ parameters
565
+ );
566
+ int parameterIndex = parameterIndex(parameters, parameter);
567
+ emitArgumentBinding(
568
+ argument,
569
+ callId,
570
+ argumentIndex,
571
+ parameterIndex,
572
+ parameter,
573
+ methodSignature(target)
574
+ );
575
+ }
576
+ }
577
+
578
+ private void emitUnresolvedArgumentBindings(
579
+ UCallExpression call,
580
+ String callId
581
+ ) {
582
+ List<UExpression> arguments = call.getValueArguments();
583
+ for (int index = 0; index < arguments.size(); index += 1) {
584
+ emitArgumentBinding(
585
+ arguments.get(index),
586
+ callId,
587
+ index,
588
+ -1,
589
+ null,
590
+ ""
591
+ );
592
+ }
593
+ }
594
+
595
+ private void emitArgumentBinding(
596
+ UExpression argument,
597
+ String callId,
598
+ int argumentIndex,
599
+ int parameterIndex,
600
+ @Nullable PsiParameter parameter,
601
+ String targetSignature
602
+ ) {
603
+ writer.emit(
604
+ "call-argument-binding",
605
+ location(argument),
606
+ callId + ":" + argumentIndex + ":" + parameterIndex,
607
+ RawJson.object(
608
+ "callRef", callId,
609
+ "argumentIndex", argumentIndex,
610
+ "argumentKind", expressionKind(argument),
611
+ "argumentSource", argument.asSourceString(),
612
+ "expression", expressionData(argument),
613
+ "isLambda", containsLambda(argument),
614
+ "bindingStatus",
615
+ parameter == null ? "unresolved" : "resolved",
616
+ "parameterIndex", parameterIndex,
617
+ "parameterName",
618
+ parameter == null
619
+ ? ""
620
+ : valueOrEmpty(parameter.getName()),
621
+ "parameterType",
622
+ parameter == null
623
+ ? ""
624
+ : typeName(parameter.getType()),
625
+ "parameterVarArgs",
626
+ parameter != null && parameter.isVarArgs(),
627
+ "targetSignature", targetSignature
628
+ )
629
+ );
630
+ }
631
+
632
+ private Map<String, Object> callData(
633
+ UCallExpression call,
634
+ @Nullable PsiMethod target
635
+ ) {
636
+ Map<String, Object> data = RawJson.object(
637
+ "language", sourceLanguage(context),
638
+ "methodName", valueOrEmpty(call.getMethodName()),
639
+ "callKind", String.valueOf(call.getKind()),
640
+ "source", call.asSourceString(),
641
+ "receiverType", typeName(call.getReceiverType()),
642
+ "receiverAccess", receiverAccess(call),
643
+ "argumentCount", call.getValueArgumentCount(),
644
+ "enclosingClass", enclosingClassName(call),
645
+ "enclosingMethod", enclosingMethodSignature(call),
646
+ "conditionRefs", enclosingConditionRefs(call),
647
+ "conditionContexts", enclosingConditionContexts(call)
648
+ );
649
+ UExpression receiver = call.getReceiver();
650
+ if (receiver != null) {
651
+ data.put("receiverSource", receiver.asSourceString());
652
+ data.put("receiverExpression", expressionData(receiver));
653
+ PsiElement resolvedReceiver = resolveExpression(receiver);
654
+ if (resolvedReceiver != null) {
655
+ data.put(
656
+ "receiverResolvedSymbol",
657
+ symbolData(resolvedReceiver)
658
+ );
659
+ }
660
+ }
661
+ putCallableScope(data, call);
662
+ if (target != null) {
663
+ PsiClass owner = target.getContainingClass();
664
+ data.put(
665
+ "targetOwnerQualifiedName",
666
+ owner == null
667
+ ? ""
668
+ : valueOrEmpty(owner.getQualifiedName())
669
+ );
670
+ data.put("targetName", target.getName());
671
+ data.put("targetSignature", methodSignature(target));
672
+ data.put("targetConstructor", target.isConstructor());
673
+ }
674
+ return data;
675
+ }
676
+
677
+ private String receiverAccess(UCallExpression call) {
678
+ UElement current = call;
679
+ UElement parent = current.getUastParent();
680
+ while (parent instanceof UQualifiedReferenceExpression qualified) {
681
+ if (isWithin(current, qualified.getSelector())) {
682
+ String name = qualified.getAccessType().getName();
683
+ if ("?.".equals(name)) {
684
+ return "safe";
685
+ }
686
+ if (".".equals(name)) {
687
+ return "simple";
688
+ }
689
+ return name == null ? "" : name.toLowerCase();
690
+ }
691
+ current = parent;
692
+ parent = current.getUastParent();
693
+ }
694
+ return "";
695
+ }
696
+
697
+ private Map<String, Object> expressionData(UExpression expression) {
698
+ if (expression instanceof UNamedExpression namedExpression) {
699
+ Map<String, Object> result = expressionData(
700
+ namedExpression.getExpression()
701
+ );
702
+ result.put(
703
+ "namedArgument",
704
+ valueOrEmpty(namedExpression.getName())
705
+ );
706
+ return result;
707
+ }
708
+
709
+ Map<String, Object> result = RawJson.object(
710
+ "kind", expressionKind(expression),
711
+ "source", expression.asSourceString(),
712
+ "expressionType", typeName(expression.getExpressionType())
713
+ );
714
+ if (expression instanceof UBinaryExpression binary) {
715
+ result.put("operator", binary.getOperator().getText());
716
+ result.put(
717
+ "left",
718
+ expressionData(binary.getLeftOperand())
719
+ );
720
+ result.put(
721
+ "right",
722
+ expressionData(binary.getRightOperand())
723
+ );
724
+ }
725
+ if (expression instanceof UIfExpression conditional) {
726
+ /*
727
+ * A Kotlin `if` is an expression and is commonly used to
728
+ * initialize a configurable Navigation start destination.
729
+ * Preserve its value branches as UAST data so downstream
730
+ * analysis can prove a finite result set without parsing the
731
+ * Kotlin source string retained for diagnostics.
732
+ */
733
+ result.put(
734
+ "conditionExpression",
735
+ expressionData(conditional.getCondition())
736
+ );
737
+ result.put(
738
+ "thenExpression",
739
+ expressionData(conditional.getThenExpression())
740
+ );
741
+ UExpression elseExpression = conditional.getElseExpression();
742
+ if (
743
+ elseExpression != null &&
744
+ !(elseExpression instanceof UastEmptyExpression)
745
+ ) {
746
+ result.put(
747
+ "elseExpression",
748
+ expressionData(elseExpression)
749
+ );
750
+ }
751
+ } else if (expression instanceof UBlockExpression block) {
752
+ List<UExpression> expressions = block.getExpressions();
753
+ if (!expressions.isEmpty()) {
754
+ UExpression terminal = expressions.get(
755
+ expressions.size() - 1
756
+ );
757
+ if (!(terminal instanceof UastEmptyExpression)) {
758
+ result.put(
759
+ "terminalExpression",
760
+ expressionData(terminal)
761
+ );
762
+ }
763
+ }
764
+ }
765
+ if (
766
+ expression
767
+ instanceof UQualifiedReferenceExpression qualified
768
+ ) {
769
+ /*
770
+ * Preserve the semantic receiver/selector split. Consumers
771
+ * can then recognize that `Screen.Home.route` and
772
+ * `Screen.Home.name` refer to the same route-owning object
773
+ * without parsing Kotlin source text or knowing either
774
+ * property name.
775
+ */
776
+ result.put(
777
+ "receiverExpression",
778
+ expressionData(qualified.getReceiver())
779
+ );
780
+ result.put(
781
+ "selectorExpression",
782
+ expressionData(qualified.getSelector())
783
+ );
784
+ }
785
+ if (expression instanceof ULambdaExpression lambda) {
786
+ result.put("callableRef", lambdaDeclarationRef(lambda));
787
+ } else if (
788
+ expression
789
+ instanceof UCallableReferenceExpression callableReference
790
+ ) {
791
+ PsiElement target = callableReference.resolve();
792
+ result.put(
793
+ "callableReferenceRef",
794
+ callableReferenceRef(callableReference, target)
795
+ );
796
+ if (target != null) {
797
+ result.put("resolvedSymbol", symbolData(target));
798
+ }
799
+ } else if (
800
+ expression instanceof UObjectLiteralExpression objectLiteral
801
+ ) {
802
+ result.put(
803
+ "classRef",
804
+ classDeclarationRef(objectLiteral.getDeclaration())
805
+ );
806
+ } else if (
807
+ expression instanceof UClassLiteralExpression classLiteral
808
+ ) {
809
+ result.put(
810
+ "classLiteral",
811
+ classLiteralData(classLiteral)
812
+ );
813
+ } else if (expression instanceof UCallExpression call) {
814
+ result.put("callRef", callRef(call));
815
+ }
816
+ if (!result.containsKey("callRef")) {
817
+ UCallExpression nestedCall = findCallExpression(expression);
818
+ if (nestedCall != null) {
819
+ result.put("callRef", callRef(nestedCall));
820
+ }
821
+ }
822
+ UClassLiteralExpression nestedClassLiteral =
823
+ findClassLiteral(expression);
824
+ if (
825
+ nestedClassLiteral != null &&
826
+ !(expression instanceof UClassLiteralExpression)
827
+ ) {
828
+ result.put(
829
+ "classLiteral",
830
+ classLiteralData(nestedClassLiteral)
831
+ );
832
+ }
833
+
834
+ PsiElement resolved = resolveExpression(expression);
835
+ if (resolved != null) {
836
+ result.put("resolvedSymbol", symbolData(resolved));
837
+ Map<String, Object> resource = resourceReference(resolved);
838
+ if (resource != null) {
839
+ result.put("resourceReference", resource);
840
+ }
841
+ }
842
+ Object constant = evaluatedConstant(expression);
843
+ if (constant != null || expression instanceof ULiteralExpression) {
844
+ result.put("constantValue", constant);
845
+ } else {
846
+ putStringComposition(result, expression);
847
+ }
848
+ return result;
849
+ }
850
+
851
+ /**
852
+ * Retains the operand structure of a non-constant String composition.
853
+ * Kotlin string templates and Java String {@code +} expressions both
854
+ * arrive as {@link UPolyadicExpression}, so downstream consumers can
855
+ * fold constant operands and preserve runtime operands without parsing
856
+ * either language's source syntax. The field is optional, keeping raw
857
+ * protocol v1 readers and older analyzer output compatible.
858
+ */
859
+ private void putStringComposition(
860
+ Map<String, Object> result,
861
+ UExpression expression
862
+ ) {
863
+ if (
864
+ !(expression instanceof UPolyadicExpression composition) ||
865
+ composition.getOperator() != UastBinaryOperator.PLUS ||
866
+ !isStringType(expression.getExpressionType())
867
+ ) {
868
+ return;
869
+ }
870
+ List<Object> operands = new ArrayList<>();
871
+ for (UExpression operand : composition.getOperands()) {
872
+ operands.add(expressionData(operand));
873
+ }
874
+ if (operands.isEmpty()) {
875
+ return;
876
+ }
877
+ result.put(
878
+ "stringComposition",
879
+ RawJson.object(
880
+ "operator", "+",
881
+ "operands", operands
882
+ )
883
+ );
884
+ }
885
+
886
+ private boolean isStringType(@Nullable PsiType type) {
887
+ String name = typeName(type);
888
+ return "java.lang.String".equals(name) ||
889
+ "kotlin.String".equals(name);
890
+ }
891
+
892
+ private Map<String, Object> classLiteralData(
893
+ UClassLiteralExpression expression
894
+ ) {
895
+ PsiType type = expression.getType();
896
+ UExpression literalExpression = expression.getExpression();
897
+ Map<String, Object> result = RawJson.object(
898
+ "type", typeName(type),
899
+ "expression", sourceOrEmpty(literalExpression),
900
+ "expressionType",
901
+ literalExpression == null
902
+ ? ""
903
+ : typeName(literalExpression.getExpressionType())
904
+ );
905
+ PsiElement resolved = literalExpression == null
906
+ ? null
907
+ : resolveExpression(literalExpression);
908
+ if (resolved instanceof PsiClass declaration) {
909
+ result.put(
910
+ "resolvedClassQualifiedName",
911
+ valueOrEmpty(declaration.getQualifiedName())
912
+ );
913
+ } else {
914
+ String typeName = typeName(type);
915
+ if (!typeName.isEmpty()) {
916
+ result.put("resolvedClassQualifiedName", typeName);
917
+ }
918
+ }
919
+ return result;
920
+ }
921
+
922
+ @Nullable
923
+ private UClassLiteralExpression findClassLiteral(
924
+ UExpression expression
925
+ ) {
926
+ if (expression instanceof UClassLiteralExpression classLiteral) {
927
+ return classLiteral;
928
+ }
929
+ if (
930
+ expression
931
+ instanceof UQualifiedReferenceExpression qualified
932
+ ) {
933
+ UClassLiteralExpression receiver = findClassLiteral(
934
+ qualified.getReceiver()
935
+ );
936
+ return receiver != null
937
+ ? receiver
938
+ : findClassLiteral(qualified.getSelector());
939
+ }
940
+ return null;
941
+ }
942
+
943
+ @Nullable
944
+ private UCallExpression findCallExpression(
945
+ UExpression expression
946
+ ) {
947
+ if (expression instanceof UCallExpression call) {
948
+ return call;
949
+ }
950
+ if (
951
+ expression
952
+ instanceof UQualifiedReferenceExpression qualified
953
+ ) {
954
+ UCallExpression selector = findCallExpression(
955
+ qualified.getSelector()
956
+ );
957
+ return selector != null
958
+ ? selector
959
+ : findCallExpression(qualified.getReceiver());
960
+ }
961
+ return null;
962
+ }
963
+
964
+ @Nullable
965
+ private Object evaluatedConstant(UExpression expression) {
966
+ Object value;
967
+ try {
968
+ value = expression.evaluate();
969
+ } catch (RuntimeException ignored) {
970
+ return null;
971
+ }
972
+ if (RawJson.isNonFiniteNumber(value)) {
973
+ return null;
974
+ }
975
+ if (
976
+ value == null ||
977
+ value instanceof String ||
978
+ value instanceof Number ||
979
+ value instanceof Boolean
980
+ ) {
981
+ return value;
982
+ }
983
+ if (value instanceof Character character) {
984
+ return character.toString();
985
+ }
986
+ return null;
987
+ }
988
+
989
+ @Nullable
990
+ private PsiElement resolveExpression(UExpression expression) {
991
+ if (expression instanceof UResolvable resolvable) {
992
+ try {
993
+ return resolvable.resolve();
994
+ } catch (RuntimeException ignored) {
995
+ return null;
996
+ }
997
+ }
998
+ return null;
999
+ }
1000
+
1001
+ private Map<String, Object> symbolData(PsiElement symbol) {
1002
+ Map<String, Object> result = RawJson.object(
1003
+ "kind", symbolKind(symbol)
1004
+ );
1005
+ if (symbol instanceof PsiMethod method) {
1006
+ result.put("name", method.getName());
1007
+ result.put("signature", methodSignature(method));
1008
+ PsiClass owner = method.getContainingClass();
1009
+ result.put(
1010
+ "ownerQualifiedName",
1011
+ owner == null
1012
+ ? ""
1013
+ : valueOrEmpty(owner.getQualifiedName())
1014
+ );
1015
+ KtProperty kotlinProperty = kotlinProperty(method);
1016
+ if (
1017
+ kotlinProperty != null &&
1018
+ method.getParameterList().getParametersCount() == 0
1019
+ ) {
1020
+ putKotlinPropertyInitializer(
1021
+ result,
1022
+ kotlinProperty
1023
+ );
1024
+ }
1025
+ } else if (symbol instanceof PsiClass declaration) {
1026
+ result.put("name", valueOrEmpty(declaration.getName()));
1027
+ result.put(
1028
+ "qualifiedName",
1029
+ valueOrEmpty(declaration.getQualifiedName())
1030
+ );
1031
+ } else if (symbol instanceof PsiVariable variable) {
1032
+ result.put("name", valueOrEmpty(variable.getName()));
1033
+ result.put("type", typeName(variable.getType()));
1034
+ result.put(
1035
+ "declarationRef",
1036
+ variableDeclarationRef(variable)
1037
+ );
1038
+ PsiElement declaration = variable.getNavigationElement();
1039
+ String declarationSource = sourceDeclarationText(
1040
+ declaration == null ? variable : declaration
1041
+ );
1042
+ String variableSource = sourceDeclarationText(variable);
1043
+ if (variableSource.length() > declarationSource.length()) {
1044
+ declarationSource = variableSource;
1045
+ }
1046
+ if (!declarationSource.isBlank()) {
1047
+ result.put("declarationSource", declarationSource);
1048
+ }
1049
+ PsiElement initializer = variable.getInitializer();
1050
+ KtProperty kotlinProperty = kotlinProperty(variable);
1051
+ if (initializer == null && kotlinProperty != null) {
1052
+ KtProperty property = kotlinProperty;
1053
+ KtExpression delegate = property.getDelegateExpression();
1054
+ if (delegate != null) {
1055
+ initializer = delegate;
1056
+ result.put("initializerKind", "delegate");
1057
+ } else if (property.getInitializer() != null) {
1058
+ initializer = property.getInitializer();
1059
+ }
1060
+ }
1061
+ String initializerSource = sourceDeclarationText(initializer);
1062
+ if (!initializerSource.isBlank()) {
1063
+ result.put("initializerSource", initializerSource);
1064
+ }
1065
+ UExpression initializerExpression =
1066
+ variableInitializerExpression(variable, initializer);
1067
+ if (initializerExpression != null) {
1068
+ result.put(
1069
+ "initializer",
1070
+ expressionData(initializerExpression)
1071
+ );
1072
+ }
1073
+ if (variable instanceof PsiField field) {
1074
+ PsiClass owner = field.getContainingClass();
1075
+ result.put(
1076
+ "ownerQualifiedName",
1077
+ owner == null
1078
+ ? ""
1079
+ : valueOrEmpty(owner.getQualifiedName())
1080
+ );
1081
+ }
1082
+ } else if (symbol instanceof PsiNamedElement named) {
1083
+ result.put("name", valueOrEmpty(named.getName()));
1084
+ }
1085
+ return result;
1086
+ }
1087
+
1088
+ @Nullable
1089
+ private UExpression variableInitializerExpression(
1090
+ PsiVariable variable,
1091
+ @Nullable PsiElement initializer
1092
+ ) {
1093
+ /*
1094
+ * Kotlin local variables commonly resolve to a light
1095
+ * PsiVariable. Its Psi initializer has source text, but cannot
1096
+ * always be converted directly to UExpression. Convert the
1097
+ * navigation declaration to UVariable first so the protocol
1098
+ * retains the structured initializer (and its callRef).
1099
+ */
1100
+ PsiElement navigation = variable.getNavigationElement();
1101
+ KtProperty kotlinProperty = kotlinProperty(variable);
1102
+ if (kotlinProperty != null) {
1103
+ KtProperty property = kotlinProperty;
1104
+ /*
1105
+ * A Kotlin delegated local (`var value by remember { ... }`)
1106
+ * has no ordinary PsiVariable initializer. The delegate is,
1107
+ * however, a first-class Kotlin PSI expression. Convert that
1108
+ * expression through UAST so consumers receive the same
1109
+ * structured callRef as for an `=` initializer; never parse
1110
+ * declaration text to recover it.
1111
+ */
1112
+ KtExpression delegatedOrInitialized =
1113
+ property.getDelegateExpressionOrInitializer();
1114
+ UExpression expression = delegatedOrInitialized == null
1115
+ ? null
1116
+ : UastContextKt.toUElement(
1117
+ delegatedOrInitialized,
1118
+ UExpression.class
1119
+ );
1120
+ if (expression != null) {
1121
+ return expression;
1122
+ }
1123
+ }
1124
+ UVariable declaration = variable instanceof UVariable uVariable
1125
+ ? uVariable
1126
+ : UastContextKt.toUElement(
1127
+ navigation,
1128
+ UVariable.class
1129
+ );
1130
+ if (
1131
+ declaration != null &&
1132
+ declaration.getUastInitializer() != null
1133
+ ) {
1134
+ return declaration.getUastInitializer();
1135
+ }
1136
+ return initializer == null
1137
+ ? null
1138
+ : UastContextKt.toUElement(
1139
+ initializer,
1140
+ UExpression.class
1141
+ );
1142
+ }
1143
+
1144
+ private String variableDeclarationRef(PsiVariable variable) {
1145
+ PsiElement declaration = variable.getNavigationElement();
1146
+ PsiElement anchor = declaration == null ? variable : declaration;
1147
+ return variableDeclarationRefs.computeIfAbsent(
1148
+ anchor,
1149
+ ignored -> writer.stableId(
1150
+ "variable-declaration",
1151
+ RawLocation.from(context, context.getLocation(anchor)),
1152
+ valueOrEmpty(variable.getName()) + ":" +
1153
+ typeName(variable.getType())
1154
+ )
1155
+ );
1156
+ }
1157
+
1158
+ @Nullable
1159
+ private KtProperty kotlinProperty(PsiElement element) {
1160
+ List<PsiElement> pending = new ArrayList<>();
1161
+ pending.add(element);
1162
+ pending.add(element.getNavigationElement());
1163
+ pending.add(element.getOriginalElement());
1164
+ Set<PsiElement> visited = java.util.Collections.newSetFromMap(
1165
+ new IdentityHashMap<>()
1166
+ );
1167
+ for (int index = 0;
1168
+ index < pending.size() && index < 32;
1169
+ index += 1) {
1170
+ PsiElement candidate = pending.get(index);
1171
+ if (candidate == null || !visited.add(candidate)) {
1172
+ continue;
1173
+ }
1174
+ if (candidate instanceof KtProperty property) {
1175
+ return property;
1176
+ }
1177
+ if (candidate instanceof KtLightElement<?, ?> light) {
1178
+ PsiElement origin = light.getKotlinOrigin();
1179
+ if (origin instanceof KtProperty property) {
1180
+ return property;
1181
+ }
1182
+ if (origin != null) {
1183
+ pending.add(origin);
1184
+ }
1185
+ }
1186
+ PsiElement parent = candidate.getParent();
1187
+ if (parent != null) {
1188
+ pending.add(parent);
1189
+ }
1190
+ }
1191
+ return null;
1192
+ }
1193
+
1194
+ private void putKotlinPropertyInitializer(
1195
+ Map<String, Object> result,
1196
+ KtProperty property
1197
+ ) {
1198
+ String declarationSource = sourceDeclarationText(property);
1199
+ if (!declarationSource.isBlank()) {
1200
+ result.put("declarationSource", declarationSource);
1201
+ }
1202
+ KtExpression initializer =
1203
+ property.getDelegateExpressionOrInitializer();
1204
+ if (initializer == null) {
1205
+ return;
1206
+ }
1207
+ if (property.getDelegateExpression() != null) {
1208
+ result.put("initializerKind", "delegate");
1209
+ }
1210
+ String initializerSource = sourceDeclarationText(initializer);
1211
+ if (!initializerSource.isBlank()) {
1212
+ result.put("initializerSource", initializerSource);
1213
+ }
1214
+ UExpression initializerExpression = UastContextKt.toUElement(
1215
+ initializer,
1216
+ UExpression.class
1217
+ );
1218
+ if (initializerExpression != null) {
1219
+ result.put(
1220
+ "initializer",
1221
+ expressionData(initializerExpression)
1222
+ );
1223
+ }
1224
+ }
1225
+
1226
+ private String sourceDeclarationText(
1227
+ @Nullable PsiElement element
1228
+ ) {
1229
+ if (
1230
+ element == null ||
1231
+ element.getContainingFile() instanceof PsiCompiledFile
1232
+ ) {
1233
+ return "";
1234
+ }
1235
+ try {
1236
+ return valueOrEmpty(element.getText());
1237
+ } catch (AssertionError | RuntimeException ignored) {
1238
+ return "";
1239
+ }
1240
+ }
1241
+
1242
+ @Nullable
1243
+ private Map<String, Object> resourceReference(PsiElement symbol) {
1244
+ if (!(symbol instanceof PsiField field)) {
1245
+ return null;
1246
+ }
1247
+ PsiClass typeClass = field.getContainingClass();
1248
+ PsiClass rClass = typeClass == null
1249
+ ? null
1250
+ : typeClass.getContainingClass();
1251
+ if (
1252
+ typeClass == null ||
1253
+ rClass == null ||
1254
+ !"R".equals(rClass.getName())
1255
+ ) {
1256
+ return null;
1257
+ }
1258
+ return RawJson.object(
1259
+ "resourceType", valueOrEmpty(typeClass.getName()),
1260
+ "resourceName", valueOrEmpty(field.getName()),
1261
+ "rClassQualifiedName",
1262
+ valueOrEmpty(rClass.getQualifiedName())
1263
+ );
1264
+ }
1265
+
1266
+ private String callRef(UCallExpression call) {
1267
+ PsiMethod target = call.resolve();
1268
+ return writer.stableId(
1269
+ target == null ? "unresolved-call" : "resolved-call",
1270
+ location(call),
1271
+ callDiscriminator(call, target)
1272
+ );
1273
+ }
1274
+
1275
+ private String lambdaDeclarationRef(ULambdaExpression lambda) {
1276
+ return writer.stableId(
1277
+ "lambda-declaration",
1278
+ location(lambda),
1279
+ lambdaDiscriminator(lambda)
1280
+ );
1281
+ }
1282
+
1283
+ private String callableReferenceRef(
1284
+ UCallableReferenceExpression reference,
1285
+ @Nullable PsiElement target
1286
+ ) {
1287
+ return writer.stableId(
1288
+ "callable-reference",
1289
+ location(reference),
1290
+ callableReferenceDiscriminator(reference, target)
1291
+ );
1292
+ }
1293
+
1294
+ private String classDeclarationRef(UClass declaration) {
1295
+ return writer.stableId(
1296
+ "class-declaration",
1297
+ location(declaration),
1298
+ classDiscriminator(declaration)
1299
+ );
1300
+ }
1301
+
1302
+ private String methodDeclarationRef(UMethod method) {
1303
+ return writer.stableId(
1304
+ "method-declaration",
1305
+ location(method),
1306
+ methodSignature(method)
1307
+ );
1308
+ }
1309
+
1310
+ private void putCallableScope(
1311
+ Map<String, Object> data,
1312
+ UElement element
1313
+ ) {
1314
+ String callable = enclosingCallableRef(element);
1315
+ if (!callable.isEmpty()) {
1316
+ data.put("enclosingCallableRef", callable);
1317
+ }
1318
+ }
1319
+
1320
+ private String enclosingCallableRef(UElement element) {
1321
+ UElement current = element.getUastParent();
1322
+ while (current != null && !(current instanceof UFile)) {
1323
+ if (current instanceof ULambdaExpression lambda) {
1324
+ return lambdaDeclarationRef(lambda);
1325
+ }
1326
+ if (current instanceof UMethod method) {
1327
+ return methodDeclarationRef(method);
1328
+ }
1329
+ current = current.getUastParent();
1330
+ }
1331
+ return "";
1332
+ }
1333
+
1334
+ private String callDiscriminator(
1335
+ UCallExpression call,
1336
+ @Nullable PsiMethod target
1337
+ ) {
1338
+ return (
1339
+ target == null
1340
+ ? valueOrEmpty(call.getMethodName())
1341
+ : methodSignature(target)
1342
+ ) + ":" + call.asSourceString();
1343
+ }
1344
+
1345
+ private String lambdaDiscriminator(ULambdaExpression lambda) {
1346
+ return enclosingMethodSignature(lambda)
1347
+ + ":"
1348
+ + lambda.asSourceString();
1349
+ }
1350
+
1351
+ private String callableReferenceDiscriminator(
1352
+ UCallableReferenceExpression reference,
1353
+ @Nullable PsiElement target
1354
+ ) {
1355
+ String targetIdentity = target instanceof PsiMethod method
1356
+ ? methodSignature(method)
1357
+ : target instanceof PsiNamedElement named
1358
+ ? valueOrEmpty(named.getName())
1359
+ : "";
1360
+ return targetIdentity + ":" + reference.asSourceString();
1361
+ }
1362
+
1363
+ private PsiParameter mappedParameter(
1364
+ UCallExpression call,
1365
+ UExpression argument,
1366
+ Map<UExpression, PsiParameter> mapping,
1367
+ PsiParameter[] parameters
1368
+ ) {
1369
+ PsiParameter direct = mapping.get(argument);
1370
+ if (direct != null) {
1371
+ return direct;
1372
+ }
1373
+ for (Map.Entry<UExpression, PsiParameter> entry
1374
+ : mapping.entrySet()) {
1375
+ if (sameSource(argument, entry.getKey())) {
1376
+ return entry.getValue();
1377
+ }
1378
+ }
1379
+ for (int index = 0; index < parameters.length; index += 1) {
1380
+ UExpression mappedArgument = call.getArgumentForParameter(index);
1381
+ if (mappedArgument != null
1382
+ && sameSource(argument, mappedArgument)) {
1383
+ return parameters[index];
1384
+ }
1385
+ }
1386
+ return null;
1387
+ }
1388
+
1389
+ private void emitCondition(
1390
+ UElement node,
1391
+ @Nullable UExpression expression,
1392
+ String conditionKind,
1393
+ String source
1394
+ ) {
1395
+ RawLocation location = location(node);
1396
+ Map<String, Object> data = RawJson.object(
1397
+ "language", sourceLanguage(context),
1398
+ "conditionKind", conditionKind,
1399
+ "expression", source,
1400
+ "expressionType",
1401
+ expression == null
1402
+ ? ""
1403
+ : typeName(expression.getExpressionType()),
1404
+ "enclosingClass", enclosingClassName(node),
1405
+ "enclosingMethod", enclosingMethodSignature(node)
1406
+ );
1407
+ if (expression != null) {
1408
+ data.put(
1409
+ "expressionStructure",
1410
+ conditionExpressionData(expression)
1411
+ );
1412
+ }
1413
+ putCallableScope(data, node);
1414
+ writer.emit(
1415
+ "condition",
1416
+ location,
1417
+ conditionKind + ":" + source,
1418
+ data
1419
+ );
1420
+ }
1421
+
1422
+ private Map<String, Object> conditionExpressionData(
1423
+ UExpression expression
1424
+ ) {
1425
+ Map<String, Object> result = expressionData(expression);
1426
+ if (expression instanceof UBinaryExpression binary) {
1427
+ result.put("operator", binary.getOperator().getText());
1428
+ result.put(
1429
+ "left",
1430
+ conditionExpressionData(binary.getLeftOperand())
1431
+ );
1432
+ result.put(
1433
+ "right",
1434
+ conditionExpressionData(binary.getRightOperand())
1435
+ );
1436
+ }
1437
+ return result;
1438
+ }
1439
+
1440
+ private List<Object> enclosingConditionRefs(UElement node) {
1441
+ Set<String> refs = new LinkedHashSet<>();
1442
+ for (Object rawContext : enclosingConditionContexts(node)) {
1443
+ if (!(rawContext instanceof Map<?, ?> context)) {
1444
+ continue;
1445
+ }
1446
+ Object conditionRef = context.get("conditionRef");
1447
+ if (conditionRef instanceof String ref && !ref.isBlank()) {
1448
+ refs.add(ref);
1449
+ }
1450
+ }
1451
+ return new ArrayList<>(refs);
1452
+ }
1453
+
1454
+ private List<Object> enclosingConditionContexts(UElement node) {
1455
+ Map<String, Object> contexts = new java.util.LinkedHashMap<>();
1456
+ UElement current = node.getUastParent();
1457
+ while (current != null && !(current instanceof UFile)) {
1458
+ if (current instanceof UIfExpression condition) {
1459
+ String ref = conditionRef(condition);
1460
+ if (isWithin(node, condition.getThenExpression())) {
1461
+ contexts.put(
1462
+ ref,
1463
+ conditionContext(
1464
+ ref,
1465
+ "then",
1466
+ "true",
1467
+ ""
1468
+ )
1469
+ );
1470
+ } else if (
1471
+ isWithin(node, condition.getElseExpression())
1472
+ ) {
1473
+ contexts.put(
1474
+ ref,
1475
+ conditionContext(
1476
+ ref,
1477
+ "else",
1478
+ "false",
1479
+ ""
1480
+ )
1481
+ );
1482
+ }
1483
+ } else if (current instanceof USwitchExpression condition) {
1484
+ USwitchClauseExpression clause = enclosingSwitchClause(
1485
+ node,
1486
+ condition
1487
+ );
1488
+ if (clause != null) {
1489
+ String ref = conditionRef(condition);
1490
+ List<Object> cases = new ArrayList<>();
1491
+ List<Object> caseStructures = new ArrayList<>();
1492
+ for (UExpression value : clause.getCaseValues()) {
1493
+ cases.add(value.asSourceString());
1494
+ caseStructures.add(
1495
+ conditionExpressionData(value)
1496
+ );
1497
+ }
1498
+ contexts.put(
1499
+ ref,
1500
+ RawJson.object(
1501
+ "conditionRef", ref,
1502
+ "branchKind", "case",
1503
+ "polarity", "case",
1504
+ "caseValues", cases,
1505
+ "caseValueStructures", caseStructures
1506
+ )
1507
+ );
1508
+ }
1509
+ }
1510
+ if (current instanceof UBlockExpression block) {
1511
+ addTerminatingFallthroughContexts(
1512
+ node,
1513
+ block,
1514
+ contexts
1515
+ );
1516
+ }
1517
+ current = current.getUastParent();
1518
+ }
1519
+ return new ArrayList<>(contexts.values());
1520
+ }
1521
+
1522
+ private void addTerminatingFallthroughContexts(
1523
+ UElement node,
1524
+ UBlockExpression block,
1525
+ Map<String, Object> contexts
1526
+ ) {
1527
+ List<UExpression> expressions = block.getExpressions();
1528
+ int containingIndex = -1;
1529
+ for (int index = 0; index < expressions.size(); index += 1) {
1530
+ if (isWithin(node, expressions.get(index))) {
1531
+ containingIndex = index;
1532
+ break;
1533
+ }
1534
+ }
1535
+ if (containingIndex < 0) {
1536
+ return;
1537
+ }
1538
+ for (int index = 0; index < containingIndex; index += 1) {
1539
+ UExpression expression = expressions.get(index);
1540
+ if (!(expression instanceof UIfExpression condition)) {
1541
+ continue;
1542
+ }
1543
+ if (
1544
+ hasElseExpression(condition) ||
1545
+ !definitelyTerminates(condition.getThenExpression())
1546
+ ) {
1547
+ continue;
1548
+ }
1549
+ String ref = conditionRef(condition);
1550
+ contexts.put(
1551
+ ref,
1552
+ conditionContext(
1553
+ ref,
1554
+ "fallthrough",
1555
+ "false",
1556
+ "then-terminates"
1557
+ )
1558
+ );
1559
+ }
1560
+ }
1561
+
1562
+ private boolean definitelyTerminates(
1563
+ @Nullable UExpression expression
1564
+ ) {
1565
+ if (
1566
+ expression instanceof UReturnExpression ||
1567
+ expression instanceof UThrowExpression
1568
+ ) {
1569
+ return true;
1570
+ }
1571
+ if (expression instanceof UBlockExpression block) {
1572
+ List<UExpression> expressions = block.getExpressions();
1573
+ return !expressions.isEmpty() &&
1574
+ definitelyTerminates(
1575
+ expressions.get(expressions.size() - 1)
1576
+ );
1577
+ }
1578
+ if (expression instanceof UIfExpression condition) {
1579
+ return hasElseExpression(condition) &&
1580
+ definitelyTerminates(condition.getThenExpression()) &&
1581
+ definitelyTerminates(condition.getElseExpression());
1582
+ }
1583
+ return false;
1584
+ }
1585
+
1586
+ private boolean hasElseExpression(UIfExpression condition) {
1587
+ UExpression expression = condition.getElseExpression();
1588
+ return expression != null &&
1589
+ !(expression instanceof UastEmptyExpression);
1590
+ }
1591
+
1592
+ private Map<String, Object> conditionContext(
1593
+ String conditionRef,
1594
+ String branchKind,
1595
+ String polarity,
1596
+ String termination
1597
+ ) {
1598
+ Map<String, Object> result = RawJson.object(
1599
+ "conditionRef", conditionRef,
1600
+ "branchKind", branchKind,
1601
+ "polarity", polarity
1602
+ );
1603
+ if (!termination.isBlank()) {
1604
+ result.put("termination", termination);
1605
+ }
1606
+ return result;
1607
+ }
1608
+
1609
+ private String conditionRef(UIfExpression condition) {
1610
+ return writer.stableId(
1611
+ "condition",
1612
+ location(condition),
1613
+ "if:" + condition.getCondition().asSourceString()
1614
+ );
1615
+ }
1616
+
1617
+ private String conditionRef(USwitchExpression condition) {
1618
+ UExpression subject = condition.getExpression();
1619
+ String source = subject == null
1620
+ ? condition.asSourceString()
1621
+ : subject.asSourceString();
1622
+ return writer.stableId(
1623
+ "condition",
1624
+ location(condition),
1625
+ "switch-or-when:" + source
1626
+ );
1627
+ }
1628
+
1629
+ @Nullable
1630
+ private USwitchClauseExpression enclosingSwitchClause(
1631
+ UElement node,
1632
+ USwitchExpression condition
1633
+ ) {
1634
+ UElement current = node;
1635
+ while (current != null && current != condition) {
1636
+ if (current instanceof USwitchClauseExpression clause) {
1637
+ return clause;
1638
+ }
1639
+ current = current.getUastParent();
1640
+ }
1641
+ return null;
1642
+ }
1643
+
1644
+ private boolean isWithin(
1645
+ UElement node,
1646
+ @Nullable UElement ancestor
1647
+ ) {
1648
+ if (ancestor == null) {
1649
+ return false;
1650
+ }
1651
+ UElement current = node;
1652
+ while (current != null) {
1653
+ if (current == ancestor) {
1654
+ return true;
1655
+ }
1656
+ current = current.getUastParent();
1657
+ }
1658
+ // Some Java/Kotlin UAST implementations materialize equivalent
1659
+ // wrapper instances when block expressions are queried. Fall
1660
+ // back to the Lint source range so sibling control-flow analysis
1661
+ // does not depend on wrapper object identity.
1662
+ RawLocation inner = location(node);
1663
+ RawLocation outer = location(ancestor);
1664
+ return inner.startOffset() >= 0 &&
1665
+ inner.endOffset() >= inner.startOffset() &&
1666
+ outer.startOffset() >= 0 &&
1667
+ outer.endOffset() >= outer.startOffset() &&
1668
+ outer.startOffset() <= inner.startOffset() &&
1669
+ outer.endOffset() >= inner.endOffset();
1670
+ }
1671
+
1672
+ private RawLocation location(UElement element) {
1673
+ return RawLocation.from(
1674
+ context,
1675
+ context.getLocation(element)
1676
+ );
1677
+ }
1678
+
1679
+ @Nullable
1680
+ private UClass enclosingClassDeclaration(UElement element) {
1681
+ UElement current = element.getUastParent();
1682
+ while (current != null && !(current instanceof UFile)) {
1683
+ if (current instanceof UClass declaration) {
1684
+ return declaration;
1685
+ }
1686
+ current = current.getUastParent();
1687
+ }
1688
+ return null;
1689
+ }
1690
+ }
1691
+
1692
+ private static int parameterIndex(
1693
+ PsiParameter[] parameters,
1694
+ @Nullable PsiParameter parameter
1695
+ ) {
1696
+ if (parameter == null) {
1697
+ return -1;
1698
+ }
1699
+ for (int index = 0; index < parameters.length; index += 1) {
1700
+ if (parameters[index] == parameter
1701
+ || parameters[index].isEquivalentTo(parameter)) {
1702
+ return index;
1703
+ }
1704
+ }
1705
+ return -1;
1706
+ }
1707
+
1708
+ private static boolean sameSource(
1709
+ UExpression left,
1710
+ UExpression right
1711
+ ) {
1712
+ if (left == right) {
1713
+ return true;
1714
+ }
1715
+ PsiElement leftSource = left.getSourcePsi();
1716
+ PsiElement rightSource = right.getSourcePsi();
1717
+ if (leftSource != null && rightSource != null) {
1718
+ return leftSource.isEquivalentTo(rightSource);
1719
+ }
1720
+ return left.asSourceString().equals(right.asSourceString());
1721
+ }
1722
+
1723
+ private static boolean containsLambda(UExpression expression) {
1724
+ if (expression instanceof ULambdaExpression) {
1725
+ return true;
1726
+ }
1727
+ if (expression instanceof UNamedExpression namedExpression) {
1728
+ return containsLambda(namedExpression.getExpression());
1729
+ }
1730
+ return false;
1731
+ }
1732
+
1733
+ private static String expressionKind(UExpression expression) {
1734
+ if (expression instanceof ULambdaExpression) {
1735
+ return "lambda";
1736
+ }
1737
+ if (expression instanceof UCallableReferenceExpression) {
1738
+ return "callable-reference";
1739
+ }
1740
+ if (expression instanceof UObjectLiteralExpression) {
1741
+ return "object-literal";
1742
+ }
1743
+ if (expression instanceof UClassLiteralExpression) {
1744
+ return "class-literal";
1745
+ }
1746
+ if (expression instanceof UCallExpression) {
1747
+ return "call";
1748
+ }
1749
+ if (expression instanceof ULiteralExpression) {
1750
+ return "literal";
1751
+ }
1752
+ if (expression instanceof UResolvable) {
1753
+ return "reference";
1754
+ }
1755
+ return "expression";
1756
+ }
1757
+
1758
+ private static String symbolKind(PsiElement symbol) {
1759
+ if (symbol instanceof PsiMethod) {
1760
+ return "method";
1761
+ }
1762
+ if (symbol instanceof PsiClass) {
1763
+ return "class";
1764
+ }
1765
+ if (symbol instanceof PsiField) {
1766
+ return "field";
1767
+ }
1768
+ if (symbol instanceof PsiParameter) {
1769
+ return "parameter";
1770
+ }
1771
+ if (symbol instanceof PsiVariable) {
1772
+ return "variable";
1773
+ }
1774
+ if (symbol instanceof PsiNamedElement) {
1775
+ return "named-element";
1776
+ }
1777
+ return "psi-element";
1778
+ }
1779
+
1780
+ private static List<Object> annotationNames(UAnnotated declaration) {
1781
+ List<Object> result = new ArrayList<>();
1782
+ declaration.getUAnnotations().forEach(annotation -> {
1783
+ String qualifiedName = annotation.getQualifiedName();
1784
+ if (qualifiedName != null && !qualifiedName.isBlank()) {
1785
+ result.add(qualifiedName);
1786
+ }
1787
+ });
1788
+ result.sort((left, right) ->
1789
+ String.valueOf(left).compareTo(String.valueOf(right))
1790
+ );
1791
+ return result;
1792
+ }
1793
+
1794
+ private static boolean isComposable(UAnnotated declaration) {
1795
+ return declaration.getUAnnotations().stream().anyMatch(annotation ->
1796
+ "androidx.compose.runtime.Composable".equals(
1797
+ annotation.getQualifiedName()
1798
+ )
1799
+ );
1800
+ }
1801
+
1802
+ private static boolean isAssignmentOperator(String operator) {
1803
+ return "=".equals(operator)
1804
+ || "+=".equals(operator)
1805
+ || "-=".equals(operator)
1806
+ || "*=".equals(operator)
1807
+ || "/=".equals(operator)
1808
+ || "%=".equals(operator)
1809
+ || "&=".equals(operator)
1810
+ || "|=".equals(operator)
1811
+ || "^=".equals(operator)
1812
+ || "<<=".equals(operator)
1813
+ || ">>=".equals(operator)
1814
+ || ">>>=".equals(operator);
1815
+ }
1816
+
1817
+ private static String classDiscriminator(UClass declaration) {
1818
+ return valueOrEmpty(declaration.getQualifiedName())
1819
+ + ":"
1820
+ + valueOrEmpty(declaration.getName());
1821
+ }
1822
+
1823
+ private static String classKind(UClass declaration) {
1824
+ if (declaration.getName() == null) {
1825
+ return "anonymous-class";
1826
+ }
1827
+ if (declaration.isAnnotationType()) {
1828
+ return "annotation";
1829
+ }
1830
+ if (declaration.isEnum()) {
1831
+ return "enum";
1832
+ }
1833
+ if (declaration.isInterface()) {
1834
+ return "interface";
1835
+ }
1836
+ return "class";
1837
+ }
1838
+
1839
+ private static String sourceLanguage(Context context) {
1840
+ String name = context.file.getName();
1841
+ if (name.endsWith(".kt")) {
1842
+ return "kotlin";
1843
+ }
1844
+ if (name.endsWith(".java")) {
1845
+ return "java";
1846
+ }
1847
+ if (name.endsWith(".xml")) {
1848
+ return "xml";
1849
+ }
1850
+ return "unknown";
1851
+ }
1852
+
1853
+ private static String methodSignature(PsiMethod method) {
1854
+ PsiClass owner = method.getContainingClass();
1855
+ String ownerName = owner == null
1856
+ ? ""
1857
+ : valueOrEmpty(owner.getQualifiedName());
1858
+ StringBuilder result = new StringBuilder(ownerName)
1859
+ .append('#')
1860
+ .append(method.getName())
1861
+ .append('(');
1862
+ PsiParameter[] parameters =
1863
+ method.getParameterList().getParameters();
1864
+ for (int index = 0; index < parameters.length; index += 1) {
1865
+ if (index > 0) {
1866
+ result.append(',');
1867
+ }
1868
+ result.append(typeName(parameters[index].getType()));
1869
+ }
1870
+ return result.append(')').toString();
1871
+ }
1872
+
1873
+ private static String enclosingClassName(UElement element) {
1874
+ UElement current = element.getUastParent();
1875
+ while (current != null) {
1876
+ if (current instanceof UClass declaration) {
1877
+ return valueOrEmpty(declaration.getQualifiedName());
1878
+ }
1879
+ current = current.getUastParent();
1880
+ }
1881
+ return "";
1882
+ }
1883
+
1884
+ private static String enclosingMethodSignature(UElement element) {
1885
+ UElement current = element.getUastParent();
1886
+ while (current != null) {
1887
+ if (current instanceof UMethod method) {
1888
+ return methodSignature(method);
1889
+ }
1890
+ current = current.getUastParent();
1891
+ }
1892
+ return "";
1893
+ }
1894
+
1895
+ private static String typeName(@Nullable PsiType type) {
1896
+ return type == null ? "" : type.getCanonicalText();
1897
+ }
1898
+
1899
+ private static String resourceFolder(XmlContext context) {
1900
+ ResourceFolderType type = context.getResourceFolderType();
1901
+ return type == null ? "" : type.getName();
1902
+ }
1903
+
1904
+ private static String valueOrEmpty(@Nullable String value) {
1905
+ return value == null ? "" : value;
1906
+ }
1907
+
1908
+ private static String sourceOrEmpty(@Nullable UElement element) {
1909
+ return element == null ? "" : element.asSourceString();
1910
+ }
1911
+ }