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.
- package/agents/api-analyzer.md +113 -0
- package/agents/app-action.md +122 -0
- package/agents/code-reviewer.md +191 -295
- package/agents/coder.md +377 -0
- package/agents/self-tester.md +8 -8
- package/agents/spec-designer.md +56 -0
- package/package.json +1 -1
- package/skills/a2h-app-req-gen/SKILL.md +174 -0
- package/skills/a2h-app-req-gen/references/spec-handoff.md +99 -0
- package/skills/a2h-app-req-gen/references/subagent-render.md +115 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/build.gradle.kts +41 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradle.properties +3 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew +251 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/gradlew.bat +94 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/settings.gradle.kts +17 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawAnalysisDetector.java +1911 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawIssueRegistry.java +37 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawJson.java +146 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawLocation.java +96 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/java/dev/reqgen/analyzer/jvm/RawRecordWriter.java +227 -0
- 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
- package/skills/a2h-app-req-gen/scripts/reqgen/analyzer-jvm/src/main/resources/dev/reqgen/analyzer/jvm/raw-record-v1.schema.json +93 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/assets/dashboard.html +151 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-lint.init.gradle +60 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/gradle/reqgen-model.init.gradle +204 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/package.json +14 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/backend.js +323 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/cli.js +811 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/gap-audit-link.js +62 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/index.js +66 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/merge.js +605 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/name-trace.js +153 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/orchestrate.js +1324 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/output-parse.js +50 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/overview.js +10 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/pool.js +23 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/post-merge-fix.js +146 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt-loader.js +51 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompt.js +967 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/prompts.md +301 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/serve.js +78 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/short-id.js +46 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/snapshot.js +267 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/agent-render/validate.js +122 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/callback-call-sites.js +62 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/build.js +1291 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/gap-audit/index.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/area-ownership.js +392 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/build.js +1995 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/callback-flow.js +277 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/component-variants.js +725 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/graph/index.js +15 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/index.js +12 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/policy.js +469 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/names/traceability.js +468 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/build.js +1066 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/req-model/index.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android-interaction-registry.js +174 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/android.js +1062 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/area-titles.js +678 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/builder.js +350 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-api-registry.js +395 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose-call-sites.js +2008 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/compose.js +9059 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/dynamic-repeated-call-sites.js +529 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/index.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/interaction-effects.js +1149 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/jvm.js +6272 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/normalize.js +33 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/pending-intents.js +565 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/resource-id.js +42 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/route-state-variants.js +781 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/state.js +234 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/analysis/source-facts/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/extract.js +40 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/index.js +6 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/manifest.js +513 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/resources.js +398 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/views.js +447 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/android/xml-parser.js +175 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/cli.js +398 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/area-graph.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/artifact.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/common.js +16 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/diagnostic.js +10 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/gap-audit.js +14 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/index.js +11 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/name-traceability.js +26 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/raw-fact.js +40 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/req-model.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/run.js +19 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/semantic-completeness.js +11 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/contracts/source-facts.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/core/output-guard.js +41 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/core/stable-id.js +92 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/dashboard-cli.js +169 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/diagnostics.js +104 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/dump.js +1028 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/dump/index.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/dump-cli.js +251 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/artifacts.js +49 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/index.js +18 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/quality-report.js +125 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/render.js +353 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/generation/types.js +2 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/graph/paths.js +474 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/index.js +15 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/protocol.js +408 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/jvm/runner.js +452 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/index.js +8 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/output.js +165 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/pipeline/run-stage1.js +891 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/discovery.js +299 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-model.js +358 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/gradle-runner.js +165 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/resource-profile.js +60 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/project/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff-cli.js +151 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/spec-handoff.js +1004 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-cli.js +109 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-input.js +419 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/stage2-name-trace-input.js +95 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/area-graph.js +444 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/index.js +7 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-model.js +580 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/req-text.js +195 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/semantic-area-graph.js +1431 -0
- package/skills/a2h-app-req-gen/scripts/reqgen/src/validation/types.js +1 -0
- package/skills/a2h-app-req-gen/scripts/reqgen-audit.mjs +298 -0
- package/skills/a2h-app-req-gen/scripts/reqgen-selfcheck.mjs +555 -0
- package/skills/a2h-code-review/SKILL.md +379 -0
- package/skills/{hmos-integration-test → a2h-integration-test}/README.md +6 -6
- package/skills/{hmos-integration-test → a2h-integration-test}/SKILL.md +6 -6
- package/skills/{hmos-integration-test → a2h-integration-test}/scripts/report-tool.mjs +1 -1
- package/skills/a2h-spec-design/SKILL.md +330 -0
- package/skills/a2h-spec-design/scripts/inspect_spec_design_inputs.mjs +207 -0
- package/skills/a2h-spec-design/scripts/move_spec_technical_reference.mjs +198 -0
- package/skills/a2h-spec-design/scripts/validate_spec_design_output.mjs +256 -0
- package/skills/a2h-spec-generate/SKILL.md +696 -0
- package/skills/a2h-spec-generate/references/trace-template.md +115 -0
- package/skills/a2h-spec-generate/scripts/parse_requirements.mjs +537 -0
- package/skills/a2h-spec-generate/template/REQ.txt +22 -0
- package/skills/a2h-spec-generate/template/REQ.xlsx +0 -0
- package/skills/a2h-spec-implement/SKILL.md +548 -0
- package/skills/a2h-spec-implement/references/harmony-pitfalls.md +186 -0
- package/skills/a2h-spec-implement/references/l1-unit-test.md +185 -0
- package/skills/a2h-spec-implement/references/l3-e2e-uitest.md +167 -0
- package/skills/a2h-spec-implement/references/test-core.md +132 -0
- package/skills/a2h-spec-implement/rules/arkts/arkts-standards.md +184 -0
- package/skills/a2h-spec-implement/rules/arkts/conventions/coding-style.md +96 -0
- package/skills/a2h-spec-implement/rules/arkts/conventions/security.md +126 -0
- package/skills/a2h-spec-implement/rules/arkts/language/arkts-rules.md +650 -0
- package/skills/a2h-spec-implement/rules/arkts/language/arkui-structure-rules.md +173 -0
- package/skills/a2h-spec-implement/rules/arkts/language/ts-to-arkts-rewrites.md +58 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/skills/a2h-spec-implement/rules/arkts/ui/component-cookbook.md +431 -0
- package/skills/a2h-spec-implement/rules/arkts/ui/state-management.md +152 -0
- package/skills/a2h-spec-implement/rules/arkts/ui/ui-quality.md +67 -0
- package/skills/a2h-spec-implement/rules/arkts/ui/ui-runtime-diagnosis.md +32 -0
- package/skills/a2h-spec-implement/rules/pipeline/build/package-set-collection.md +43 -0
- package/skills/a2h-spec-implement/scripts/_lib/common.mjs +97 -0
- package/skills/a2h-spec-implement/scripts/_lib/config.mjs +70 -0
- package/skills/a2h-spec-implement/scripts/_lib/lessons_cli.mjs +48 -0
- package/skills/a2h-spec-implement/scripts/build_lessons.mjs +24 -0
- package/skills/a2h-spec-implement/scripts/code_pattern_lessons.mjs +26 -0
- package/skills/a2h-spec-implement/scripts/device_ui.mjs +433 -0
- package/skills/a2h-spec-implement/scripts/device_ui_core.mjs +895 -0
- package/skills/a2h-spec-implement/scripts/ledger/aggregate_run.mjs +207 -0
- package/skills/a2h-spec-implement/scripts/manifest/emit_summary_line.mjs +37 -0
- package/skills/a2h-spec-implement/scripts/manifest/finalize_run.mjs +115 -0
- package/skills/a2h-spec-implement/scripts/manifest/render_final_summary.mjs +78 -0
- package/skills/a2h-spec-implement/scripts/manifest/root_mirror.mjs +137 -0
- package/skills/a2h-spec-implement/scripts/package.json +12 -0
- package/skills/a2h-spec-implement/scripts/state_propagation_check.mjs +676 -0
- package/skills/a2h-spec-implement/scripts/test/l2_run.mjs +244 -0
- package/skills/a2h-spec-implement/scripts/test/tp_coverage.mjs +231 -0
- package/skills/a2h-spec-implement/scripts/ui_memory.mjs +161 -0
- package/skills/a2h-spec-implement/scripts/utils/ensure_app.mjs +134 -0
- package/skills/a2h-ui-transfer/SKILL.md +20 -9
- package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +80 -0
- package/skills/hmos-convert-pipeline/SKILL.md +7 -5
- package/skills/hmos-incremental-ui-align/README.md +7 -7
- package/skills/hmos-incremental-ui-align/SKILL.md +1 -1
- package/skills/hmos-incremental-ui-align/page_align.md +1 -1
- package/skills/hmos-spec-generate/SKILL.md +2 -2
- package/skills/hmos-spec-generate/scripts/parse_requirements.mjs +537 -0
- package/skills/hmos-fix-build-errors/SKILL.md +0 -266
- package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +0 -219
- package/skills/hmos-fix-build-errors/references/known-patterns.md +0 -157
- package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +0 -131
- package/skills/hmos-spec-generate/scripts/parse_requirements.ts +0 -515
- /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/resolve-metadata-tool.mjs +0 -0
- /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/self-test-runner.mjs +0 -0
- /package/skills/{hmos-integration-test → a2h-integration-test}/scripts/testcases-tool.mjs +0 -0
|
@@ -0,0 +1,2008 @@
|
|
|
1
|
+
import {
|
|
2
|
+
advanceCallSiteStack,
|
|
3
|
+
executionFlowsForward,
|
|
4
|
+
isCallSiteArgumentStep,
|
|
5
|
+
} from "../callback-call-sites.js";
|
|
6
|
+
import { dynamicRepeatedCallSiteContexts } from "./dynamic-repeated-call-sites.js";
|
|
7
|
+
function object(value) {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
9
|
+
? value
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
function text(record, key) {
|
|
13
|
+
const value = record.data[key];
|
|
14
|
+
return typeof value === "string" && value.trim().length > 0
|
|
15
|
+
? value.trim()
|
|
16
|
+
: undefined;
|
|
17
|
+
}
|
|
18
|
+
function expression(binding) {
|
|
19
|
+
return object(binding.data.expression) ?? {};
|
|
20
|
+
}
|
|
21
|
+
function expressionText(binding, key) {
|
|
22
|
+
const value = expression(binding)[key];
|
|
23
|
+
return typeof value === "string" && value.trim().length > 0
|
|
24
|
+
? value.trim()
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
27
|
+
function structuredSymbolKey(summary) {
|
|
28
|
+
const symbol = object(summary?.resolvedSymbol);
|
|
29
|
+
if (!symbol) return undefined;
|
|
30
|
+
const part = (key) =>
|
|
31
|
+
typeof symbol[key] === "string" ? symbol[key].trim() : "";
|
|
32
|
+
const name = part("name");
|
|
33
|
+
const signature = part("signature");
|
|
34
|
+
if (!name && !signature) return undefined;
|
|
35
|
+
const initializer = object(symbol.initializer);
|
|
36
|
+
const declarationIdentity =
|
|
37
|
+
part("declarationRef") ||
|
|
38
|
+
part("declarationId") ||
|
|
39
|
+
(typeof initializer?.callRef === "string"
|
|
40
|
+
? initializer.callRef.trim()
|
|
41
|
+
: "");
|
|
42
|
+
return [
|
|
43
|
+
part("kind"),
|
|
44
|
+
part("ownerQualifiedName"),
|
|
45
|
+
signature || name,
|
|
46
|
+
part("type"),
|
|
47
|
+
...(declarationIdentity ? [declarationIdentity] : []),
|
|
48
|
+
].join("\u0000");
|
|
49
|
+
}
|
|
50
|
+
function stateRecordSymbol(record) {
|
|
51
|
+
if (record.kind === "condition") {
|
|
52
|
+
return structuredSymbolKey(object(record.data.expressionStructure));
|
|
53
|
+
}
|
|
54
|
+
if (record.kind === "assignment") {
|
|
55
|
+
return structuredSymbolKey(object(record.data.left));
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
function stateRecordScope(index, record) {
|
|
60
|
+
const enclosingMethod = text(record, "enclosingMethod");
|
|
61
|
+
if (enclosingMethod) return enclosingMethod;
|
|
62
|
+
const visited = new Set();
|
|
63
|
+
let callableRef = text(record, "enclosingCallableRef");
|
|
64
|
+
while (callableRef && !visited.has(callableRef)) {
|
|
65
|
+
visited.add(callableRef);
|
|
66
|
+
const callable = index.recordsById.get(callableRef);
|
|
67
|
+
if (callable?.kind === "method-declaration") {
|
|
68
|
+
return text(callable, "signature") ?? callable.id;
|
|
69
|
+
}
|
|
70
|
+
callableRef = index.callableParent.get(callableRef);
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
function directBooleanCallGuard(index, call) {
|
|
75
|
+
const rawContexts = call.data.conditionContexts;
|
|
76
|
+
if (!Array.isArray(rawContexts) || rawContexts.length !== 1) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const context = object(rawContexts[0]);
|
|
80
|
+
const conditionRef =
|
|
81
|
+
typeof context?.conditionRef === "string"
|
|
82
|
+
? context.conditionRef.trim()
|
|
83
|
+
: "";
|
|
84
|
+
if (
|
|
85
|
+
!conditionRef ||
|
|
86
|
+
context?.branchKind !== "then" ||
|
|
87
|
+
context.polarity !== "true" ||
|
|
88
|
+
(Array.isArray(context.caseValues) && context.caseValues.length > 0)
|
|
89
|
+
)
|
|
90
|
+
return undefined;
|
|
91
|
+
const condition = index.recordsById.get(conditionRef);
|
|
92
|
+
const structure = object(condition?.data.expressionStructure);
|
|
93
|
+
const symbol = object(structure?.resolvedSymbol);
|
|
94
|
+
const expressionValue = condition ? text(condition, "expression") : undefined;
|
|
95
|
+
const booleanTypes = new Set([
|
|
96
|
+
"boolean",
|
|
97
|
+
"java.lang.Boolean",
|
|
98
|
+
"kotlin.Boolean",
|
|
99
|
+
]);
|
|
100
|
+
const isBoolean = [
|
|
101
|
+
condition?.data.expressionType,
|
|
102
|
+
structure?.expressionType,
|
|
103
|
+
symbol?.type,
|
|
104
|
+
].some(
|
|
105
|
+
(value) => typeof value === "string" && booleanTypes.has(value.trim()),
|
|
106
|
+
);
|
|
107
|
+
const stateSymbol = structuredSymbolKey(structure);
|
|
108
|
+
if (
|
|
109
|
+
!condition ||
|
|
110
|
+
!structure ||
|
|
111
|
+
!symbol ||
|
|
112
|
+
!expressionValue ||
|
|
113
|
+
!isBoolean ||
|
|
114
|
+
!stateSymbol
|
|
115
|
+
)
|
|
116
|
+
return undefined;
|
|
117
|
+
const matches = [...index.recordsById.values()].filter(
|
|
118
|
+
(record) =>
|
|
119
|
+
(record.kind === "condition" || record.kind === "assignment") &&
|
|
120
|
+
stateRecordSymbol(record) === stateSymbol,
|
|
121
|
+
);
|
|
122
|
+
const scopes = new Set(
|
|
123
|
+
matches.map((record) => stateRecordScope(index, record)),
|
|
124
|
+
);
|
|
125
|
+
if (matches.length === 0 || scopes.has(undefined) || scopes.size !== 1) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
return { condition, stateSymbol, expression: expressionValue };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Returns the complete structured branch context of one exact local
|
|
132
|
+
* Composable invocation. Unlike surface-state inference this does not try to
|
|
133
|
+
* identify a mutable state slot: the narrower claim is simply that controls
|
|
134
|
+
* materialized for this call site exist only while every enclosing branch
|
|
135
|
+
* guard holds.
|
|
136
|
+
*/
|
|
137
|
+
function callSiteVisibilityGuards(index, call, inlineGuardsByCall = new Map()) {
|
|
138
|
+
const inline = inlineGuardsByCall.get(call.id) ?? [];
|
|
139
|
+
const rawContexts = call.data.conditionContexts;
|
|
140
|
+
if (!Array.isArray(rawContexts) || rawContexts.length === 0) return inline;
|
|
141
|
+
const guards = rawContexts.flatMap((rawContext) => {
|
|
142
|
+
const context = object(rawContext);
|
|
143
|
+
const conditionRef =
|
|
144
|
+
typeof context?.conditionRef === "string"
|
|
145
|
+
? context.conditionRef.trim()
|
|
146
|
+
: "";
|
|
147
|
+
const polarity = context?.polarity;
|
|
148
|
+
const branchKind = context?.branchKind;
|
|
149
|
+
const caseValues = Array.isArray(context?.caseValues)
|
|
150
|
+
? context.caseValues.filter((value) => typeof value === "string")
|
|
151
|
+
: [];
|
|
152
|
+
if (
|
|
153
|
+
!conditionRef ||
|
|
154
|
+
!["then", "else", "fallthrough", "case"].includes(
|
|
155
|
+
typeof branchKind === "string" ? branchKind : "",
|
|
156
|
+
) ||
|
|
157
|
+
!["true", "false", "case"].includes(
|
|
158
|
+
typeof polarity === "string" ? polarity : "",
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
return [];
|
|
162
|
+
const condition = index.recordsById.get(conditionRef);
|
|
163
|
+
const expressionValue = condition
|
|
164
|
+
? text(condition, "expression")
|
|
165
|
+
: undefined;
|
|
166
|
+
if (
|
|
167
|
+
!condition ||
|
|
168
|
+
!expressionValue ||
|
|
169
|
+
!object(condition.data.expressionStructure)
|
|
170
|
+
)
|
|
171
|
+
return [];
|
|
172
|
+
const expressionAnchor =
|
|
173
|
+
polarity === "false"
|
|
174
|
+
? `!(${expressionValue})`
|
|
175
|
+
: polarity === "case"
|
|
176
|
+
? `${expressionValue} in (${caseValues.join(", ")})`
|
|
177
|
+
: expressionValue;
|
|
178
|
+
const plainLanguage =
|
|
179
|
+
polarity === "false"
|
|
180
|
+
? `不满足条件:${expressionValue}`
|
|
181
|
+
: polarity === "case"
|
|
182
|
+
? `条件 ${expressionValue} 匹配:${caseValues.join("、")}`
|
|
183
|
+
: `满足条件:${expressionValue}`;
|
|
184
|
+
return [{ condition, expressionAnchor, plainLanguage }];
|
|
185
|
+
});
|
|
186
|
+
const structured = guards.length === rawContexts.length ? guards : [];
|
|
187
|
+
return [
|
|
188
|
+
...new Map(
|
|
189
|
+
[...structured, ...inline].map((guard) => [
|
|
190
|
+
`${guard.expressionAnchor}\u0000${guard.condition.id}`,
|
|
191
|
+
guard,
|
|
192
|
+
]),
|
|
193
|
+
).values(),
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
function bindingCallableRef(binding) {
|
|
197
|
+
return (
|
|
198
|
+
expressionText(binding, "callableRef") ??
|
|
199
|
+
expressionText(binding, "callableReferenceRef")
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
function booleanExpressionBinding(binding) {
|
|
203
|
+
const summary = expression(binding);
|
|
204
|
+
const symbol = object(summary.resolvedSymbol);
|
|
205
|
+
return [summary.expressionType, symbol?.type].some(
|
|
206
|
+
(value) =>
|
|
207
|
+
typeof value === "string" &&
|
|
208
|
+
/(?:^|\.)(?:Boolean|boolean)$/u.test(value.trim()),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
function normalizedExpressionSource(binding) {
|
|
212
|
+
const summary = expression(binding);
|
|
213
|
+
const source =
|
|
214
|
+
typeof summary.source === "string"
|
|
215
|
+
? summary.source.trim()
|
|
216
|
+
: text(binding, "argumentSource");
|
|
217
|
+
return source && source.length > 0 ? source : undefined;
|
|
218
|
+
}
|
|
219
|
+
function animatedVisibilityBoundary(index, call) {
|
|
220
|
+
if (
|
|
221
|
+
call.kind !== "resolved-call" ||
|
|
222
|
+
sourceApiName(call) !== "AnimatedVisibility" ||
|
|
223
|
+
!(text(call, "targetOwnerQualifiedName") ?? "").startsWith(
|
|
224
|
+
"androidx.compose.animation.",
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
return undefined;
|
|
228
|
+
const bindings = bindingsFor(index, call.id);
|
|
229
|
+
const visible = bindings.filter(
|
|
230
|
+
(binding) =>
|
|
231
|
+
!bindingCallableRef(binding) && booleanExpressionBinding(binding),
|
|
232
|
+
);
|
|
233
|
+
const content = bindings.filter(
|
|
234
|
+
(binding) => bindingCallableRef(binding) !== undefined,
|
|
235
|
+
);
|
|
236
|
+
if (visible.length !== 1 || content.length !== 1) return undefined;
|
|
237
|
+
const scopeRef = bindingCallableRef(content[0]);
|
|
238
|
+
const expressionAnchor = normalizedExpressionSource(visible[0]);
|
|
239
|
+
if (!scopeRef || !expressionAnchor) return undefined;
|
|
240
|
+
const symbol = structuredSymbolKey(expression(visible[0]));
|
|
241
|
+
return {
|
|
242
|
+
scopeRef,
|
|
243
|
+
canonicalKey: `${symbol ?? expressionAnchor}\u0000${expressionAnchor}`,
|
|
244
|
+
guard: {
|
|
245
|
+
condition: visible[0],
|
|
246
|
+
expressionAnchor,
|
|
247
|
+
plainLanguage: `仅在 ${expressionAnchor} 为 true 时显示`,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function enclosingComposable(index, callableRef) {
|
|
252
|
+
const visited = new Set();
|
|
253
|
+
let current = callableRef;
|
|
254
|
+
while (current && !visited.has(current)) {
|
|
255
|
+
visited.add(current);
|
|
256
|
+
const method = index.methodsByCallable.get(current);
|
|
257
|
+
if (method) return method;
|
|
258
|
+
current = index.callableParent.get(current);
|
|
259
|
+
}
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
function sameVisibilityGuards(left, right) {
|
|
263
|
+
return (
|
|
264
|
+
left.length === right.length &&
|
|
265
|
+
left.every(
|
|
266
|
+
(guard, index) => guard.canonicalKey === right[index].canonicalKey,
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Propagates an AnimatedVisibility boolean guard through exact local
|
|
272
|
+
* Composable calls only when every runtime invocation of the declaration has
|
|
273
|
+
* the same complete guard set. AnimatedVisibility remains inline UI; this map
|
|
274
|
+
* annotates controls and never creates a Surface/Area.
|
|
275
|
+
*/
|
|
276
|
+
function animatedVisibilityGuardsByCall(index) {
|
|
277
|
+
const boundaries = index.calls.flatMap((call) => {
|
|
278
|
+
const boundary = animatedVisibilityBoundary(index, call);
|
|
279
|
+
return boundary ? [boundary] : [];
|
|
280
|
+
});
|
|
281
|
+
const lexicalGuards = (call) =>
|
|
282
|
+
boundaries
|
|
283
|
+
.filter((boundary) =>
|
|
284
|
+
scopeIsWithin(
|
|
285
|
+
index,
|
|
286
|
+
text(call, "enclosingCallableRef"),
|
|
287
|
+
boundary.scopeRef,
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
.sort((left, right) =>
|
|
291
|
+
left.canonicalKey.localeCompare(right.canonicalKey, "en"),
|
|
292
|
+
);
|
|
293
|
+
const inheritedByMethod = new Map();
|
|
294
|
+
const effective = (call) => {
|
|
295
|
+
const method = enclosingComposable(
|
|
296
|
+
index,
|
|
297
|
+
text(call, "enclosingCallableRef"),
|
|
298
|
+
);
|
|
299
|
+
const inherited = method
|
|
300
|
+
? (inheritedByMethod.get(method.record.id) ?? [])
|
|
301
|
+
: [];
|
|
302
|
+
return [
|
|
303
|
+
...new Map(
|
|
304
|
+
[...lexicalGuards(call), ...inherited].map((guard) => [
|
|
305
|
+
guard.canonicalKey,
|
|
306
|
+
guard,
|
|
307
|
+
]),
|
|
308
|
+
).values(),
|
|
309
|
+
].sort((left, right) =>
|
|
310
|
+
left.canonicalKey.localeCompare(right.canonicalKey, "en"),
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
for (
|
|
314
|
+
let iteration = 0;
|
|
315
|
+
iteration < index.methodsByCallable.size;
|
|
316
|
+
iteration += 1
|
|
317
|
+
) {
|
|
318
|
+
let changed = false;
|
|
319
|
+
for (const target of index.methodsByCallable.values()) {
|
|
320
|
+
const invocations = index.calls.filter(
|
|
321
|
+
(call) =>
|
|
322
|
+
targetForCall(index, call)?.record.id === target.record.id &&
|
|
323
|
+
!scopeIsWithinPreview(index, text(call, "enclosingCallableRef")),
|
|
324
|
+
);
|
|
325
|
+
if (invocations.length === 0) continue;
|
|
326
|
+
const guards = invocations.map(effective);
|
|
327
|
+
if (
|
|
328
|
+
guards[0].length === 0 ||
|
|
329
|
+
!guards.every((candidate) => sameVisibilityGuards(candidate, guards[0]))
|
|
330
|
+
)
|
|
331
|
+
continue;
|
|
332
|
+
const previous = inheritedByMethod.get(target.record.id) ?? [];
|
|
333
|
+
if (sameVisibilityGuards(previous, guards[0])) continue;
|
|
334
|
+
inheritedByMethod.set(target.record.id, guards[0]);
|
|
335
|
+
changed = true;
|
|
336
|
+
}
|
|
337
|
+
if (!changed) break;
|
|
338
|
+
}
|
|
339
|
+
return new Map(
|
|
340
|
+
index.calls.flatMap((call) => {
|
|
341
|
+
const guards = effective(call);
|
|
342
|
+
return guards.length > 0
|
|
343
|
+
? [[call.id, guards.map((item) => item.guard)]]
|
|
344
|
+
: [];
|
|
345
|
+
}),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
function sourceApiName(record) {
|
|
349
|
+
return text(record, "targetName")?.replace(/-.*$/u, "");
|
|
350
|
+
}
|
|
351
|
+
function composableQualifiedName(record) {
|
|
352
|
+
const owner = text(record, "ownerQualifiedName");
|
|
353
|
+
const name = text(record, "name");
|
|
354
|
+
return name ? (owner ? `${owner}#${name}` : name) : undefined;
|
|
355
|
+
}
|
|
356
|
+
function isComposableDeclaration(record) {
|
|
357
|
+
if (record.kind !== "method-declaration") return false;
|
|
358
|
+
if (record.data.composable === true) return true;
|
|
359
|
+
return (
|
|
360
|
+
Array.isArray(record.data.annotations) &&
|
|
361
|
+
record.data.annotations.includes("androidx.compose.runtime.Composable")
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
function append(map, key, value) {
|
|
365
|
+
const values = map.get(key) ?? [];
|
|
366
|
+
values.push(value);
|
|
367
|
+
map.set(key, values);
|
|
368
|
+
}
|
|
369
|
+
function buildIndex(android, jvm, state) {
|
|
370
|
+
const recordsById = new Map();
|
|
371
|
+
const calls = [];
|
|
372
|
+
const mutableBindings = new Map();
|
|
373
|
+
const callableParent = new Map();
|
|
374
|
+
const methodsBySignature = new Map();
|
|
375
|
+
const methodsByCallable = new Map();
|
|
376
|
+
const mutableByName = new Map();
|
|
377
|
+
for (const record of jvm.body) {
|
|
378
|
+
recordsById.set(record.id, record);
|
|
379
|
+
if (record.kind === "resolved-call") calls.push(record);
|
|
380
|
+
if (record.kind === "call-argument-binding") {
|
|
381
|
+
const callRef = text(record, "callRef");
|
|
382
|
+
if (callRef) append(mutableBindings, callRef, record);
|
|
383
|
+
}
|
|
384
|
+
if (
|
|
385
|
+
record.kind === "method-declaration" ||
|
|
386
|
+
record.kind === "lambda-declaration"
|
|
387
|
+
) {
|
|
388
|
+
const parent = text(record, "enclosingCallableRef");
|
|
389
|
+
if (parent) callableParent.set(record.id, parent);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const record of jvm.body) {
|
|
393
|
+
if (!isComposableDeclaration(record)) continue;
|
|
394
|
+
const qualifiedName = composableQualifiedName(record);
|
|
395
|
+
const signature = text(record, "signature") ?? qualifiedName;
|
|
396
|
+
const componentRef = qualifiedName
|
|
397
|
+
? state.componentByQualifiedName.get(qualifiedName)
|
|
398
|
+
: undefined;
|
|
399
|
+
if (!qualifiedName || !signature || !componentRef) continue;
|
|
400
|
+
const method = { record, componentRef, qualifiedName, signature };
|
|
401
|
+
methodsByCallable.set(record.id, method);
|
|
402
|
+
methodsBySignature.set(signature, method);
|
|
403
|
+
const candidates = mutableByName.get(qualifiedName) ?? [];
|
|
404
|
+
candidates.push(method);
|
|
405
|
+
mutableByName.set(qualifiedName, candidates);
|
|
406
|
+
}
|
|
407
|
+
const resourceValues = new Map();
|
|
408
|
+
for (const value of android.values) {
|
|
409
|
+
if (typeof value.value !== "string" || value.value.trim().length === 0) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const key = `${value.resourceType}/${value.name}`;
|
|
413
|
+
const values = resourceValues.get(key) ?? new Set();
|
|
414
|
+
values.add(value.value);
|
|
415
|
+
resourceValues.set(key, values);
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
recordsById,
|
|
419
|
+
calls: calls.sort((left, right) => left.id.localeCompare(right.id, "en")),
|
|
420
|
+
bindingsByCall: new Map(
|
|
421
|
+
[...mutableBindings].map(([key, values]) => [
|
|
422
|
+
key,
|
|
423
|
+
values.sort((left, right) => left.id.localeCompare(right.id, "en")),
|
|
424
|
+
]),
|
|
425
|
+
),
|
|
426
|
+
callableParent,
|
|
427
|
+
methodsByCallable,
|
|
428
|
+
methodsBySignature,
|
|
429
|
+
methodsByQualifiedName: new Map(
|
|
430
|
+
[...mutableByName].map(([key, values]) => [
|
|
431
|
+
key,
|
|
432
|
+
values.sort((left, right) =>
|
|
433
|
+
left.signature.localeCompare(right.signature, "en"),
|
|
434
|
+
),
|
|
435
|
+
]),
|
|
436
|
+
),
|
|
437
|
+
resourceValues: new Map(
|
|
438
|
+
[...resourceValues].map(([key, values]) => [
|
|
439
|
+
key,
|
|
440
|
+
[...values].sort((left, right) => left.localeCompare(right, "en")),
|
|
441
|
+
]),
|
|
442
|
+
),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function targetForCall(index, call) {
|
|
446
|
+
const signature = text(call, "targetSignature");
|
|
447
|
+
const exact = signature ? index.methodsBySignature.get(signature) : undefined;
|
|
448
|
+
if (exact) return exact;
|
|
449
|
+
if (!signature?.includes("androidx.compose.runtime.Composer")) {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
const owner = text(call, "targetOwnerQualifiedName");
|
|
453
|
+
const name = sourceApiName(call);
|
|
454
|
+
if (!owner || !name) return undefined;
|
|
455
|
+
const candidates = index.methodsByQualifiedName.get(`${owner}#${name}`) ?? [];
|
|
456
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
457
|
+
}
|
|
458
|
+
function bindingsFor(index, callRef) {
|
|
459
|
+
return index.bindingsByCall.get(callRef) ?? [];
|
|
460
|
+
}
|
|
461
|
+
function parameterName(binding) {
|
|
462
|
+
return (
|
|
463
|
+
text(binding, "parameterName") ?? expressionText(binding, "namedArgument")
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
function scopeIsWithin(index, candidate, ancestor) {
|
|
467
|
+
const visited = new Set();
|
|
468
|
+
let current = candidate;
|
|
469
|
+
while (current && !visited.has(current)) {
|
|
470
|
+
if (current === ancestor) return true;
|
|
471
|
+
visited.add(current);
|
|
472
|
+
current = index.callableParent.get(current);
|
|
473
|
+
}
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
const composePreviewAnnotations = new Set([
|
|
477
|
+
"androidx.compose.ui.tooling.preview.Preview",
|
|
478
|
+
"org.jetbrains.compose.ui.tooling.preview.Preview",
|
|
479
|
+
]);
|
|
480
|
+
function scopeIsWithinPreview(index, candidate) {
|
|
481
|
+
const visited = new Set();
|
|
482
|
+
let current = candidate;
|
|
483
|
+
while (current && !visited.has(current)) {
|
|
484
|
+
visited.add(current);
|
|
485
|
+
const callable = index.recordsById.get(current);
|
|
486
|
+
const annotations = Array.isArray(callable?.data.annotations)
|
|
487
|
+
? callable.data.annotations
|
|
488
|
+
: [];
|
|
489
|
+
if (
|
|
490
|
+
annotations.some(
|
|
491
|
+
(annotation) =>
|
|
492
|
+
typeof annotation === "string" &&
|
|
493
|
+
composePreviewAnnotations.has(annotation),
|
|
494
|
+
)
|
|
495
|
+
)
|
|
496
|
+
return true;
|
|
497
|
+
current = index.callableParent.get(current);
|
|
498
|
+
}
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
function reachableComponents(index, root) {
|
|
502
|
+
const methods = new Map([[root.record.id, root]]);
|
|
503
|
+
const pending = [root];
|
|
504
|
+
while (pending.length > 0) {
|
|
505
|
+
const current = pending.shift();
|
|
506
|
+
for (const call of index.calls) {
|
|
507
|
+
if (
|
|
508
|
+
!scopeIsWithin(
|
|
509
|
+
index,
|
|
510
|
+
text(call, "enclosingCallableRef"),
|
|
511
|
+
current.record.id,
|
|
512
|
+
)
|
|
513
|
+
)
|
|
514
|
+
continue;
|
|
515
|
+
const target = targetForCall(index, call);
|
|
516
|
+
if (!target || methods.has(target.record.id)) continue;
|
|
517
|
+
methods.set(target.record.id, target);
|
|
518
|
+
pending.push(target);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return new Set([...methods.values()].map((method) => method.componentRef));
|
|
522
|
+
}
|
|
523
|
+
function sourceKind(record) {
|
|
524
|
+
const language = text(record, "language");
|
|
525
|
+
if (language === "kotlin" || record.location?.path.endsWith(".kt")) {
|
|
526
|
+
return "kotlin";
|
|
527
|
+
}
|
|
528
|
+
if (language === "java" || record.location?.path.endsWith(".java")) {
|
|
529
|
+
return "java";
|
|
530
|
+
}
|
|
531
|
+
return "other";
|
|
532
|
+
}
|
|
533
|
+
function recordLocation(record, anchor, symbol) {
|
|
534
|
+
if (!record.location) return undefined;
|
|
535
|
+
return {
|
|
536
|
+
sourceKind: sourceKind(record),
|
|
537
|
+
path: record.location.path,
|
|
538
|
+
anchor,
|
|
539
|
+
range: {
|
|
540
|
+
startLine: Math.max(1, record.location.startLine),
|
|
541
|
+
endLine: Math.max(record.location.startLine, record.location.endLine),
|
|
542
|
+
...(record.location.startColumn >= 1
|
|
543
|
+
? { startColumn: record.location.startColumn }
|
|
544
|
+
: {}),
|
|
545
|
+
...(record.location.endColumn >= 1
|
|
546
|
+
? { endColumn: record.location.endColumn }
|
|
547
|
+
: {}),
|
|
548
|
+
},
|
|
549
|
+
...(symbol ? { symbol } : {}),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function resourceValue(index, summary) {
|
|
553
|
+
const reference = object(summary.resourceReference);
|
|
554
|
+
const type = reference?.resourceType;
|
|
555
|
+
const name = reference?.resourceName;
|
|
556
|
+
if (typeof type !== "string" || typeof name !== "string") return undefined;
|
|
557
|
+
const values = index.resourceValues.get(`${type}/${name}`) ?? [];
|
|
558
|
+
return values.length === 1 ? values[0] : undefined;
|
|
559
|
+
}
|
|
560
|
+
function staticLabel(index, binding, visited = new Set()) {
|
|
561
|
+
const summary = expression(binding);
|
|
562
|
+
const constant = summary.constantValue;
|
|
563
|
+
if (typeof constant === "string" && constant.trim().length > 0) {
|
|
564
|
+
return { value: constant, support: [binding] };
|
|
565
|
+
}
|
|
566
|
+
const selected = resourceValue(index, summary);
|
|
567
|
+
if (selected) return { value: selected, support: [binding] };
|
|
568
|
+
const initializer = object(object(summary.resolvedSymbol)?.initializer);
|
|
569
|
+
if (initializer) {
|
|
570
|
+
const value = initializer.constantValue;
|
|
571
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
572
|
+
return { value, support: [binding] };
|
|
573
|
+
}
|
|
574
|
+
const initializedResource = resourceValue(index, initializer);
|
|
575
|
+
if (initializedResource) {
|
|
576
|
+
return { value: initializedResource, support: [binding] };
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const nestedRef = expressionText(binding, "callRef");
|
|
580
|
+
if (!nestedRef || visited.has(nestedRef)) return { support: [] };
|
|
581
|
+
const nested = index.recordsById.get(nestedRef);
|
|
582
|
+
if (!nested) return { support: [] };
|
|
583
|
+
const nextVisited = new Set(visited).add(nestedRef);
|
|
584
|
+
const candidates = bindingsFor(index, nestedRef)
|
|
585
|
+
.map((candidate) => staticLabel(index, candidate, nextVisited))
|
|
586
|
+
.filter((candidate) => candidate.value !== undefined);
|
|
587
|
+
const values = [...new Set(candidates.map((candidate) => candidate.value))];
|
|
588
|
+
return values.length === 1
|
|
589
|
+
? {
|
|
590
|
+
value: values[0],
|
|
591
|
+
support: [
|
|
592
|
+
binding,
|
|
593
|
+
nested,
|
|
594
|
+
...candidates.flatMap((item) => item.support),
|
|
595
|
+
],
|
|
596
|
+
}
|
|
597
|
+
: { support: [] };
|
|
598
|
+
}
|
|
599
|
+
function namingRole(name) {
|
|
600
|
+
const normalized = name.replace(/[^A-Za-z0-9]/gu, "").toLowerCase();
|
|
601
|
+
if (["text", "title", "label"].includes(normalized)) return "visibleText";
|
|
602
|
+
if (
|
|
603
|
+
["contentdescription", "accessibilitylabel", "semanticslabel"].includes(
|
|
604
|
+
normalized,
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
return "contentDescription";
|
|
608
|
+
if (["hint", "placeholder"].includes(normalized)) return "associatedLabel";
|
|
609
|
+
return undefined;
|
|
610
|
+
}
|
|
611
|
+
function normalizedSemanticParameterName(name) {
|
|
612
|
+
return name.replace(/[^A-Za-z0-9]/gu, "").toLowerCase();
|
|
613
|
+
}
|
|
614
|
+
function callSiteNaming(
|
|
615
|
+
index,
|
|
616
|
+
call,
|
|
617
|
+
callbackParameterName,
|
|
618
|
+
allowOrdinaryLabel,
|
|
619
|
+
) {
|
|
620
|
+
const grouped = new Map();
|
|
621
|
+
const callbackSpecific = [];
|
|
622
|
+
const callbackLabel = `${normalizedSemanticParameterName(callbackParameterName)}label`;
|
|
623
|
+
for (const binding of bindingsFor(index, call.id)) {
|
|
624
|
+
const name = parameterName(binding);
|
|
625
|
+
if (!name) continue;
|
|
626
|
+
const normalized = normalizedSemanticParameterName(name);
|
|
627
|
+
if (normalized === callbackLabel) {
|
|
628
|
+
callbackSpecific.push(binding);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const role = allowOrdinaryLabel ? namingRole(name) : undefined;
|
|
632
|
+
if (!role) continue;
|
|
633
|
+
const values = grouped.get(role) ?? [];
|
|
634
|
+
values.push(binding);
|
|
635
|
+
grouped.set(role, values);
|
|
636
|
+
}
|
|
637
|
+
const resolveBindings = (bindings) => {
|
|
638
|
+
const candidates = bindings.map((binding) => ({
|
|
639
|
+
binding,
|
|
640
|
+
resolution: staticLabel(index, binding),
|
|
641
|
+
}));
|
|
642
|
+
const values = [
|
|
643
|
+
...new Set(
|
|
644
|
+
candidates.flatMap(({ resolution }) =>
|
|
645
|
+
resolution.value ? [resolution.value] : [],
|
|
646
|
+
),
|
|
647
|
+
),
|
|
648
|
+
];
|
|
649
|
+
if (values.length !== 1) return { support: [] };
|
|
650
|
+
return {
|
|
651
|
+
value: values[0],
|
|
652
|
+
support: candidates
|
|
653
|
+
.filter(({ resolution }) => resolution.value === values[0])
|
|
654
|
+
.flatMap(({ binding, resolution }) => [binding, ...resolution.support]),
|
|
655
|
+
};
|
|
656
|
+
};
|
|
657
|
+
const specific = resolveBindings(callbackSpecific);
|
|
658
|
+
if (callbackSpecific.length > 0) {
|
|
659
|
+
return specific.value
|
|
660
|
+
? {
|
|
661
|
+
contentDescription: specific.value,
|
|
662
|
+
support: [
|
|
663
|
+
...new Map(
|
|
664
|
+
specific.support.map((record) => [record.id, record]),
|
|
665
|
+
).values(),
|
|
666
|
+
],
|
|
667
|
+
}
|
|
668
|
+
: { support: [] };
|
|
669
|
+
}
|
|
670
|
+
const visible = resolveBindings(grouped.get("visibleText") ?? []);
|
|
671
|
+
const description = resolveBindings(grouped.get("contentDescription") ?? []);
|
|
672
|
+
const associated = resolveBindings(grouped.get("associatedLabel") ?? []);
|
|
673
|
+
return {
|
|
674
|
+
...(visible.value ? { visibleText: visible.value } : {}),
|
|
675
|
+
...(description.value ? { contentDescription: description.value } : {}),
|
|
676
|
+
...(associated.value ? { associatedLabel: associated.value } : {}),
|
|
677
|
+
support: [
|
|
678
|
+
...new Map(
|
|
679
|
+
[...visible.support, ...description.support, ...associated.support].map(
|
|
680
|
+
(record) => [record.id, record],
|
|
681
|
+
),
|
|
682
|
+
).values(),
|
|
683
|
+
],
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function exactParameterReference(summary, parameterName) {
|
|
687
|
+
const symbol = object(summary?.resolvedSymbol);
|
|
688
|
+
return symbol?.kind === "parameter" && symbol.name === parameterName;
|
|
689
|
+
}
|
|
690
|
+
function exactLambdaParameter(record, ordinal) {
|
|
691
|
+
const parameters = Array.isArray(record?.data.parameters)
|
|
692
|
+
? record.data.parameters
|
|
693
|
+
: [];
|
|
694
|
+
const parameter = object(parameters[ordinal]);
|
|
695
|
+
const name = typeof parameter?.name === "string" ? parameter.name.trim() : "";
|
|
696
|
+
const type = typeof parameter?.type === "string" ? parameter.type.trim() : "";
|
|
697
|
+
return name && type ? { name, type } : undefined;
|
|
698
|
+
}
|
|
699
|
+
function enumEntriesType(summary) {
|
|
700
|
+
const receiver = object(summary?.receiverExpression);
|
|
701
|
+
const receiverSymbol = object(receiver?.resolvedSymbol);
|
|
702
|
+
const qualifiedName = receiverSymbol?.qualifiedName;
|
|
703
|
+
if (
|
|
704
|
+
receiverSymbol?.kind !== "class" ||
|
|
705
|
+
typeof qualifiedName !== "string" ||
|
|
706
|
+
qualifiedName.trim().length === 0
|
|
707
|
+
)
|
|
708
|
+
return undefined;
|
|
709
|
+
const enumType = qualifiedName.trim();
|
|
710
|
+
const selector = object(summary?.selectorExpression);
|
|
711
|
+
const selectorSymbol =
|
|
712
|
+
object(selector?.resolvedSymbol) ?? object(summary?.resolvedSymbol);
|
|
713
|
+
if (
|
|
714
|
+
selectorSymbol?.name === "getEntries" ||
|
|
715
|
+
selectorSymbol?.name === "entries"
|
|
716
|
+
)
|
|
717
|
+
return enumType;
|
|
718
|
+
/*
|
|
719
|
+
* Kotlin UAST can resolve the synthetic `entries` selector back to the enum
|
|
720
|
+
* class instead of its generated getter. Accept that malformed symbol only
|
|
721
|
+
* when the remaining structured facts prove the same operation: the exact
|
|
722
|
+
* class receiver, the single selector token, and EnumEntries of that class.
|
|
723
|
+
* The conjunction keeps this a language-level compatibility path rather
|
|
724
|
+
* than a source-text or product-specific guess.
|
|
725
|
+
*/
|
|
726
|
+
const selectorSource =
|
|
727
|
+
typeof selector?.source === "string" ? selector.source.trim() : "";
|
|
728
|
+
const expressionType =
|
|
729
|
+
typeof summary?.expressionType === "string"
|
|
730
|
+
? summary.expressionType.replace(/\s+/gu, "").trim()
|
|
731
|
+
: "";
|
|
732
|
+
return selectorSource === "entries" &&
|
|
733
|
+
expressionType === `kotlin.enums.EnumEntries<${enumType}>` &&
|
|
734
|
+
selectorSymbol?.kind === "class" &&
|
|
735
|
+
selectorSymbol.qualifiedName === enumType
|
|
736
|
+
? enumType
|
|
737
|
+
: undefined;
|
|
738
|
+
}
|
|
739
|
+
function locationContains(container, nested) {
|
|
740
|
+
if (
|
|
741
|
+
!container.location ||
|
|
742
|
+
!nested.location ||
|
|
743
|
+
container.location.path !== nested.location.path
|
|
744
|
+
)
|
|
745
|
+
return false;
|
|
746
|
+
return (
|
|
747
|
+
nested.location.startOffset >= container.location.startOffset &&
|
|
748
|
+
nested.location.endOffset <= container.location.endOffset
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
function finiteEnumLoopForCall(index, child) {
|
|
752
|
+
const childScope = text(child, "enclosingCallableRef");
|
|
753
|
+
const candidates = index.calls.flatMap((call) => {
|
|
754
|
+
if (
|
|
755
|
+
call.kind !== "resolved-call" ||
|
|
756
|
+
sourceApiName(call) !== "forEach" ||
|
|
757
|
+
!(text(call, "targetOwnerQualifiedName") ?? "").startsWith(
|
|
758
|
+
"kotlin.collections.",
|
|
759
|
+
)
|
|
760
|
+
)
|
|
761
|
+
return [];
|
|
762
|
+
const actionBindings = bindingsFor(index, call.id).filter(
|
|
763
|
+
(binding) =>
|
|
764
|
+
parameterName(binding) === "action" &&
|
|
765
|
+
bindingCallableRef(binding) !== undefined,
|
|
766
|
+
);
|
|
767
|
+
if (actionBindings.length !== 1) return [];
|
|
768
|
+
const actionRef = bindingCallableRef(actionBindings[0]);
|
|
769
|
+
const action = index.recordsById.get(actionRef);
|
|
770
|
+
const parameter = exactLambdaParameter(action, 0);
|
|
771
|
+
const enumType = enumEntriesType(object(call.data.receiverExpression));
|
|
772
|
+
return action &&
|
|
773
|
+
parameter &&
|
|
774
|
+
enumType &&
|
|
775
|
+
parameter.type === enumType &&
|
|
776
|
+
scopeIsWithin(index, childScope, actionRef)
|
|
777
|
+
? [
|
|
778
|
+
{
|
|
779
|
+
call,
|
|
780
|
+
actionBinding: actionBindings[0],
|
|
781
|
+
action,
|
|
782
|
+
parameter,
|
|
783
|
+
enumType,
|
|
784
|
+
},
|
|
785
|
+
]
|
|
786
|
+
: [];
|
|
787
|
+
});
|
|
788
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
789
|
+
}
|
|
790
|
+
function caseContextsForCondition(record, conditionRef) {
|
|
791
|
+
const contexts = Array.isArray(record.data.conditionContexts)
|
|
792
|
+
? record.data.conditionContexts
|
|
793
|
+
: [];
|
|
794
|
+
return contexts.flatMap((value) => {
|
|
795
|
+
const context = object(value);
|
|
796
|
+
return context?.conditionRef === conditionRef ? [context] : [];
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Expands one exact enum.entries/forEach invocation only when a source-level
|
|
801
|
+
* `when(loopParameter)` provides a complete set of static, uniquely named
|
|
802
|
+
* case branches for the call-site label. Dynamic/else/multiple-loop shapes
|
|
803
|
+
* remain one unresolved control instead of being guessed.
|
|
804
|
+
*/
|
|
805
|
+
function finiteCallSiteNamingVariants(index, call) {
|
|
806
|
+
const loop = finiteEnumLoopForCall(index, call);
|
|
807
|
+
if (!loop) return undefined;
|
|
808
|
+
const labelBindings = bindingsFor(index, call.id).filter((binding) => {
|
|
809
|
+
const name = parameterName(binding);
|
|
810
|
+
return (
|
|
811
|
+
name !== undefined &&
|
|
812
|
+
namingRole(name) === "visibleText" &&
|
|
813
|
+
bindingCallableRef(binding) === undefined
|
|
814
|
+
);
|
|
815
|
+
});
|
|
816
|
+
if (labelBindings.length !== 1) return undefined;
|
|
817
|
+
const labelBinding = labelBindings[0];
|
|
818
|
+
const conditions = [...index.recordsById.values()].filter(
|
|
819
|
+
(record) =>
|
|
820
|
+
record.kind === "condition" &&
|
|
821
|
+
record.data.conditionKind === "switch-or-when" &&
|
|
822
|
+
text(record, "enclosingCallableRef") === loop.action.id &&
|
|
823
|
+
exactParameterReference(
|
|
824
|
+
object(record.data.expressionStructure),
|
|
825
|
+
loop.parameter.name,
|
|
826
|
+
) &&
|
|
827
|
+
locationContains(labelBinding, record),
|
|
828
|
+
);
|
|
829
|
+
if (conditions.length !== 1) return undefined;
|
|
830
|
+
const condition = conditions[0];
|
|
831
|
+
const caseCalls = index.calls.filter(
|
|
832
|
+
(candidate) =>
|
|
833
|
+
locationContains(labelBinding, candidate) &&
|
|
834
|
+
caseContextsForCondition(candidate, condition.id).length > 0,
|
|
835
|
+
);
|
|
836
|
+
if (caseCalls.length < 2) return undefined;
|
|
837
|
+
const labelsByField = new Map();
|
|
838
|
+
for (const caseCall of caseCalls) {
|
|
839
|
+
const contexts = caseContextsForCondition(caseCall, condition.id);
|
|
840
|
+
if (
|
|
841
|
+
contexts.length !== 1 ||
|
|
842
|
+
contexts[0].branchKind !== "case" ||
|
|
843
|
+
contexts[0].polarity !== "case"
|
|
844
|
+
)
|
|
845
|
+
return undefined;
|
|
846
|
+
const structures = Array.isArray(contexts[0].caseValueStructures)
|
|
847
|
+
? contexts[0].caseValueStructures.flatMap((value) => {
|
|
848
|
+
const structure = object(value);
|
|
849
|
+
return structure ? [structure] : [];
|
|
850
|
+
})
|
|
851
|
+
: [];
|
|
852
|
+
if (structures.length === 0) return undefined;
|
|
853
|
+
const candidates = bindingsFor(index, caseCall.id)
|
|
854
|
+
.map((binding) => ({ binding, label: staticLabel(index, binding) }))
|
|
855
|
+
.filter((candidate) => candidate.label.value !== undefined);
|
|
856
|
+
const values = [
|
|
857
|
+
...new Set(candidates.map((candidate) => candidate.label.value)),
|
|
858
|
+
];
|
|
859
|
+
if (values.length !== 1) return undefined;
|
|
860
|
+
for (const structure of structures) {
|
|
861
|
+
const symbol = object(structure.resolvedSymbol);
|
|
862
|
+
const field = typeof symbol?.name === "string" ? symbol.name.trim() : "";
|
|
863
|
+
if (
|
|
864
|
+
symbol?.kind !== "field" ||
|
|
865
|
+
symbol.ownerQualifiedName !== loop.enumType ||
|
|
866
|
+
!field ||
|
|
867
|
+
labelsByField.has(field)
|
|
868
|
+
)
|
|
869
|
+
return undefined;
|
|
870
|
+
labelsByField.set(field, {
|
|
871
|
+
visibleText: values[0],
|
|
872
|
+
support: [
|
|
873
|
+
loop.call,
|
|
874
|
+
loop.actionBinding,
|
|
875
|
+
condition,
|
|
876
|
+
caseCall,
|
|
877
|
+
labelBinding,
|
|
878
|
+
...candidates.flatMap((candidate) => [
|
|
879
|
+
candidate.binding,
|
|
880
|
+
...candidate.label.support,
|
|
881
|
+
]),
|
|
882
|
+
],
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
const variants = [...labelsByField.values()];
|
|
887
|
+
if (
|
|
888
|
+
variants.length < 2 ||
|
|
889
|
+
variants.length > 64 ||
|
|
890
|
+
new Set(variants.map((variant) => variant.visibleText)).size !==
|
|
891
|
+
variants.length
|
|
892
|
+
)
|
|
893
|
+
return undefined;
|
|
894
|
+
return variants;
|
|
895
|
+
}
|
|
896
|
+
function hasMoreSpecificFiniteInvocation(index, target, templateOwnerRef) {
|
|
897
|
+
return index.calls.some((candidate) => {
|
|
898
|
+
if (
|
|
899
|
+
!scopeIsWithin(
|
|
900
|
+
index,
|
|
901
|
+
text(candidate, "enclosingCallableRef"),
|
|
902
|
+
target.record.id,
|
|
903
|
+
)
|
|
904
|
+
)
|
|
905
|
+
return false;
|
|
906
|
+
const nestedTarget = targetForCall(index, candidate);
|
|
907
|
+
return (
|
|
908
|
+
nestedTarget !== undefined &&
|
|
909
|
+
reachableComponents(index, nestedTarget).has(templateOwnerRef) &&
|
|
910
|
+
finiteCallSiteNamingVariants(index, candidate) !== undefined
|
|
911
|
+
);
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
function declaredParameterNames(record) {
|
|
915
|
+
const parameters = Array.isArray(record.data.parameters)
|
|
916
|
+
? record.data.parameters
|
|
917
|
+
: [];
|
|
918
|
+
return new Set(
|
|
919
|
+
parameters.flatMap((value) => {
|
|
920
|
+
const parameter = object(value);
|
|
921
|
+
const name = parameter?.name;
|
|
922
|
+
return typeof name === "string" && name.trim().length > 0
|
|
923
|
+
? [name.trim()]
|
|
924
|
+
: [];
|
|
925
|
+
}),
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Finds the lexical declaration that owns a referenced parameter. Merely
|
|
930
|
+
* knowing the enclosing method is insufficient: a nested lambda may declare
|
|
931
|
+
* a same-named parameter and must shadow the outer slot.
|
|
932
|
+
*/
|
|
933
|
+
function nearestParameterDeclaration(index, callableRef, parameterName) {
|
|
934
|
+
const visited = new Set();
|
|
935
|
+
let current = callableRef;
|
|
936
|
+
while (current && !visited.has(current)) {
|
|
937
|
+
visited.add(current);
|
|
938
|
+
const declaration = index.recordsById.get(current);
|
|
939
|
+
if (!declaration) return undefined;
|
|
940
|
+
if (declaredParameterNames(declaration).has(parameterName)) {
|
|
941
|
+
return declaration;
|
|
942
|
+
}
|
|
943
|
+
current = index.callableParent.get(current);
|
|
944
|
+
}
|
|
945
|
+
return undefined;
|
|
946
|
+
}
|
|
947
|
+
function concreteCallback(state, index, binding) {
|
|
948
|
+
const callable = expressionText(binding, "callableRef");
|
|
949
|
+
if (callable) return state.callbackForRawCallable(callable);
|
|
950
|
+
const referenceRef = expressionText(binding, "callableReferenceRef");
|
|
951
|
+
const reference = referenceRef
|
|
952
|
+
? index.recordsById.get(referenceRef)
|
|
953
|
+
: undefined;
|
|
954
|
+
const targetCallable = reference
|
|
955
|
+
? text(reference, "targetCallableRef")
|
|
956
|
+
: undefined;
|
|
957
|
+
if (targetCallable) return state.callbackForRawCallable(targetCallable);
|
|
958
|
+
const signature = reference ? text(reference, "targetSignature") : undefined;
|
|
959
|
+
if (!signature) return undefined;
|
|
960
|
+
const matches = state.builder
|
|
961
|
+
.facts("callback")
|
|
962
|
+
.filter(
|
|
963
|
+
(fact) =>
|
|
964
|
+
fact.kind === "callback" && fact.data.callableSymbol === signature,
|
|
965
|
+
);
|
|
966
|
+
return matches.length === 1 ? matches[0].id : undefined;
|
|
967
|
+
}
|
|
968
|
+
function executionAdjacency(facts, index, callbacks, state) {
|
|
969
|
+
const mutable = new Map();
|
|
970
|
+
const add = (from, step) => {
|
|
971
|
+
const targets = mutable.get(from) ?? new Map();
|
|
972
|
+
const key = [
|
|
973
|
+
step.callbackKey,
|
|
974
|
+
step.flowKind,
|
|
975
|
+
step.parameterName ?? "",
|
|
976
|
+
step.callSiteKey ?? "",
|
|
977
|
+
].join("\u0000");
|
|
978
|
+
targets.set(key, step);
|
|
979
|
+
mutable.set(from, targets);
|
|
980
|
+
};
|
|
981
|
+
for (const fact of facts) {
|
|
982
|
+
const forward = executionFlowsForward(fact.data.flowKind);
|
|
983
|
+
const from = forward ? fact.data.fromCallbackKey : fact.data.toCallbackKey;
|
|
984
|
+
const to = forward ? fact.data.toCallbackKey : fact.data.fromCallbackKey;
|
|
985
|
+
add(from, {
|
|
986
|
+
callbackKey: to,
|
|
987
|
+
flowKind: fact.data.flowKind,
|
|
988
|
+
...(fact.data.parameterName
|
|
989
|
+
? { parameterName: fact.data.parameterName }
|
|
990
|
+
: {}),
|
|
991
|
+
...(fact.data.callSiteKey ? { callSiteKey: fact.data.callSiteKey } : {}),
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
/*
|
|
995
|
+
* Callback facts deliberately share source callable identities, while the
|
|
996
|
+
* JVM invocation normalizer can conservatively reject some `invoke()`
|
|
997
|
+
* records. This compensation is still lexical: it connects only the nearest
|
|
998
|
+
* declaration that actually owns the receiver parameter. A lambda-owned
|
|
999
|
+
* parameter therefore shadows, rather than aliases, an outer formal slot.
|
|
1000
|
+
*/
|
|
1001
|
+
for (const call of index.calls) {
|
|
1002
|
+
if (
|
|
1003
|
+
sourceApiName(call) !== "invoke" ||
|
|
1004
|
+
!/(?:^|\.)Function\d+$/u.test(
|
|
1005
|
+
text(call, "targetOwnerQualifiedName") ?? "",
|
|
1006
|
+
)
|
|
1007
|
+
)
|
|
1008
|
+
continue;
|
|
1009
|
+
const receiver = object(call.data.receiverResolvedSymbol);
|
|
1010
|
+
const name = typeof receiver?.name === "string" ? receiver.name.trim() : "";
|
|
1011
|
+
if (receiver?.kind !== "parameter" || name.length === 0) continue;
|
|
1012
|
+
const rawCaller = text(call, "enclosingCallableRef");
|
|
1013
|
+
const caller = rawCaller
|
|
1014
|
+
? state.callbackForRawCallable(rawCaller)
|
|
1015
|
+
: undefined;
|
|
1016
|
+
if (!caller || !rawCaller) continue;
|
|
1017
|
+
const declaration = nearestParameterDeclaration(index, rawCaller, name);
|
|
1018
|
+
if (!declaration || declaration.kind !== "method-declaration") continue;
|
|
1019
|
+
const signature = text(declaration, "signature");
|
|
1020
|
+
const qualified = composableQualifiedName(declaration);
|
|
1021
|
+
if (!signature && !qualified) continue;
|
|
1022
|
+
const symbols = new Set([
|
|
1023
|
+
...(signature ? [`${signature}#parameter:${name}`] : []),
|
|
1024
|
+
...(qualified ? [`${qualified}#parameter:${name}`] : []),
|
|
1025
|
+
]);
|
|
1026
|
+
for (const formal of callbacks) {
|
|
1027
|
+
if (symbols.has(formal.data.callableSymbol)) {
|
|
1028
|
+
add(caller, {
|
|
1029
|
+
callbackKey: formal.id,
|
|
1030
|
+
flowKind: "listener-invocation",
|
|
1031
|
+
parameterName: name,
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return new Map(
|
|
1037
|
+
[...mutable].map(([key, values]) => [
|
|
1038
|
+
key,
|
|
1039
|
+
[...values.values()].sort(
|
|
1040
|
+
(left, right) =>
|
|
1041
|
+
left.callbackKey.localeCompare(right.callbackKey, "en") ||
|
|
1042
|
+
left.flowKind.localeCompare(right.flowKind, "en") ||
|
|
1043
|
+
(left.callSiteKey ?? "").localeCompare(right.callSiteKey ?? "", "en"),
|
|
1044
|
+
),
|
|
1045
|
+
]),
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
function copyTemplateSemantics(
|
|
1049
|
+
state,
|
|
1050
|
+
templateRef,
|
|
1051
|
+
instanceRef,
|
|
1052
|
+
properties,
|
|
1053
|
+
conditions,
|
|
1054
|
+
location,
|
|
1055
|
+
) {
|
|
1056
|
+
for (const property of properties) {
|
|
1057
|
+
if (property.data.ownerKey !== templateRef) continue;
|
|
1058
|
+
state.builder.addFact("ui-property", {
|
|
1059
|
+
identity: [
|
|
1060
|
+
{ kind: "control", value: instanceRef },
|
|
1061
|
+
{ kind: "semantic-key", value: property.data.property },
|
|
1062
|
+
],
|
|
1063
|
+
confidence: property.confidence,
|
|
1064
|
+
support: [
|
|
1065
|
+
{
|
|
1066
|
+
location,
|
|
1067
|
+
role: "call-site-specialized Compose control property",
|
|
1068
|
+
},
|
|
1069
|
+
],
|
|
1070
|
+
data: {
|
|
1071
|
+
...property.data,
|
|
1072
|
+
ownerKey: instanceRef,
|
|
1073
|
+
},
|
|
1074
|
+
relatedFactRefs: [instanceRef],
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
for (const condition of conditions) {
|
|
1078
|
+
if (condition.data.ownerKey !== templateRef) continue;
|
|
1079
|
+
state.builder.addFact("condition", {
|
|
1080
|
+
identity: [
|
|
1081
|
+
{ kind: "control", value: instanceRef },
|
|
1082
|
+
{
|
|
1083
|
+
kind: "semantic-key",
|
|
1084
|
+
value: condition.data.conditionRole ?? "enabled",
|
|
1085
|
+
},
|
|
1086
|
+
{ kind: "semantic-key", value: condition.data.expressionAnchor },
|
|
1087
|
+
],
|
|
1088
|
+
confidence: condition.confidence,
|
|
1089
|
+
support: [
|
|
1090
|
+
{
|
|
1091
|
+
location,
|
|
1092
|
+
role: "call-site-specialized Compose control condition",
|
|
1093
|
+
},
|
|
1094
|
+
],
|
|
1095
|
+
data: {
|
|
1096
|
+
...condition.data,
|
|
1097
|
+
ownerKey: instanceRef,
|
|
1098
|
+
},
|
|
1099
|
+
relatedFactRefs: [instanceRef],
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
function specializeCallSiteSurfaces(
|
|
1104
|
+
state,
|
|
1105
|
+
index,
|
|
1106
|
+
call,
|
|
1107
|
+
target,
|
|
1108
|
+
actions,
|
|
1109
|
+
directSurfaceCount,
|
|
1110
|
+
componentById,
|
|
1111
|
+
properties,
|
|
1112
|
+
conditions,
|
|
1113
|
+
location,
|
|
1114
|
+
) {
|
|
1115
|
+
const instances = new Map();
|
|
1116
|
+
const templateRefs = [
|
|
1117
|
+
...new Set(
|
|
1118
|
+
actions.flatMap((action) =>
|
|
1119
|
+
action.surfaceTemplateRef ? [action.surfaceTemplateRef] : [],
|
|
1120
|
+
),
|
|
1121
|
+
),
|
|
1122
|
+
].sort((left, right) => left.localeCompare(right, "en"));
|
|
1123
|
+
// One guard controlling multiple distinct surfaces does not identify which
|
|
1124
|
+
// surface the state activates. Preserve that ambiguity instead of adding
|
|
1125
|
+
// the same activation to every target.
|
|
1126
|
+
const directGuard =
|
|
1127
|
+
templateRefs.length === 1 && directSurfaceCount === 1
|
|
1128
|
+
? directBooleanCallGuard(index, call)
|
|
1129
|
+
: undefined;
|
|
1130
|
+
for (const templateRef of templateRefs) {
|
|
1131
|
+
const template = componentById.get(templateRef);
|
|
1132
|
+
if (!template) continue;
|
|
1133
|
+
const hostRefs = [
|
|
1134
|
+
...new Set(
|
|
1135
|
+
actions
|
|
1136
|
+
.filter((action) => action.surfaceTemplateRef === templateRef)
|
|
1137
|
+
.map((action) => action.actualFact.data.ownerKey),
|
|
1138
|
+
),
|
|
1139
|
+
];
|
|
1140
|
+
if (hostRefs.length !== 1) continue;
|
|
1141
|
+
const [hostRef] = hostRefs;
|
|
1142
|
+
const host = componentById.get(hostRef);
|
|
1143
|
+
const anchor = state.builder
|
|
1144
|
+
.stableId("fact", "compose-call-site-surface-anchor", [
|
|
1145
|
+
{ kind: "component", value: hostRef },
|
|
1146
|
+
{ kind: "component", value: templateRef },
|
|
1147
|
+
{ kind: "symbol", value: target.signature },
|
|
1148
|
+
// Source text is not a call-site identity: two invocations can be
|
|
1149
|
+
// byte-for-byte identical. The JVM extractor record id includes the
|
|
1150
|
+
// deterministic source path/range and therefore keeps those physical
|
|
1151
|
+
// invocations as distinct surface instances.
|
|
1152
|
+
{ kind: "semantic-key", value: call.id },
|
|
1153
|
+
])
|
|
1154
|
+
.split("/")
|
|
1155
|
+
.at(-1);
|
|
1156
|
+
const qualifiedName =
|
|
1157
|
+
`${host?.data.qualifiedName ?? hostRef}` +
|
|
1158
|
+
`#surface-call:${template.data.componentType}:${anchor}`;
|
|
1159
|
+
const instanceRef = state.ensureComponent(
|
|
1160
|
+
qualifiedName,
|
|
1161
|
+
template.data.componentType,
|
|
1162
|
+
location,
|
|
1163
|
+
"exact invocation of a reusable Compose surface",
|
|
1164
|
+
"high",
|
|
1165
|
+
{
|
|
1166
|
+
uiTechnology: template.data.uiTechnology ?? "compose",
|
|
1167
|
+
analysisRole: "call-site-instance",
|
|
1168
|
+
},
|
|
1169
|
+
);
|
|
1170
|
+
state.builder.mergeFactData("component", templateRef, {
|
|
1171
|
+
analysisRole: "declaration-template",
|
|
1172
|
+
});
|
|
1173
|
+
state.builder.addFact("container-binding", {
|
|
1174
|
+
identity: [
|
|
1175
|
+
{ kind: "component", value: hostRef },
|
|
1176
|
+
{ kind: "container", value: "surface" },
|
|
1177
|
+
{ kind: "component", value: instanceRef },
|
|
1178
|
+
],
|
|
1179
|
+
confidence: "high",
|
|
1180
|
+
support: [
|
|
1181
|
+
{
|
|
1182
|
+
location,
|
|
1183
|
+
role: "call-site-specialized Compose surface ownership",
|
|
1184
|
+
},
|
|
1185
|
+
],
|
|
1186
|
+
data: {
|
|
1187
|
+
bindingKind: "surface",
|
|
1188
|
+
hostKey: hostRef,
|
|
1189
|
+
contentKeys: [instanceRef],
|
|
1190
|
+
},
|
|
1191
|
+
relatedFactRefs: [hostRef, instanceRef],
|
|
1192
|
+
});
|
|
1193
|
+
const naming = callSiteNaming(index, call, "__surface", true);
|
|
1194
|
+
if (naming.visibleText) {
|
|
1195
|
+
state.builder.addFact("ui-property", {
|
|
1196
|
+
identity: [
|
|
1197
|
+
{ kind: "component", value: instanceRef },
|
|
1198
|
+
{ kind: "semantic-key", value: "title" },
|
|
1199
|
+
],
|
|
1200
|
+
confidence: "high",
|
|
1201
|
+
support: [
|
|
1202
|
+
{
|
|
1203
|
+
location,
|
|
1204
|
+
role: "statically resolved call-site surface title",
|
|
1205
|
+
},
|
|
1206
|
+
],
|
|
1207
|
+
data: {
|
|
1208
|
+
ownerKey: instanceRef,
|
|
1209
|
+
property: "title",
|
|
1210
|
+
value: naming.visibleText,
|
|
1211
|
+
},
|
|
1212
|
+
relatedFactRefs: [instanceRef],
|
|
1213
|
+
});
|
|
1214
|
+
} else {
|
|
1215
|
+
for (const property of properties) {
|
|
1216
|
+
if (property.data.ownerKey !== templateRef) continue;
|
|
1217
|
+
state.builder.addFact("ui-property", {
|
|
1218
|
+
identity: [
|
|
1219
|
+
{ kind: "component", value: instanceRef },
|
|
1220
|
+
{ kind: "semantic-key", value: property.data.property },
|
|
1221
|
+
],
|
|
1222
|
+
confidence: property.confidence,
|
|
1223
|
+
support: [
|
|
1224
|
+
{
|
|
1225
|
+
location,
|
|
1226
|
+
role: "call-site-specialized Compose surface property",
|
|
1227
|
+
},
|
|
1228
|
+
],
|
|
1229
|
+
data: { ...property.data, ownerKey: instanceRef },
|
|
1230
|
+
relatedFactRefs: [instanceRef],
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
for (const condition of conditions) {
|
|
1235
|
+
if (condition.data.ownerKey !== templateRef) continue;
|
|
1236
|
+
state.builder.addFact("condition", {
|
|
1237
|
+
identity: [
|
|
1238
|
+
{ kind: "component", value: instanceRef },
|
|
1239
|
+
{ kind: "semantic-key", value: condition.data.expressionAnchor },
|
|
1240
|
+
],
|
|
1241
|
+
confidence: condition.confidence,
|
|
1242
|
+
support: [
|
|
1243
|
+
{
|
|
1244
|
+
location,
|
|
1245
|
+
role: "structured condition guarding exact local @Composable surface invocation",
|
|
1246
|
+
},
|
|
1247
|
+
],
|
|
1248
|
+
data: { ...condition.data, ownerKey: instanceRef },
|
|
1249
|
+
relatedFactRefs: [instanceRef],
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
if (directGuard) {
|
|
1253
|
+
const guardLocation =
|
|
1254
|
+
recordLocation(
|
|
1255
|
+
directGuard.condition,
|
|
1256
|
+
`compose-call-site-surface-condition:${call.id}`,
|
|
1257
|
+
target.signature,
|
|
1258
|
+
) ?? location;
|
|
1259
|
+
state.builder.addFact("condition", {
|
|
1260
|
+
identity: [
|
|
1261
|
+
{ kind: "component", value: instanceRef },
|
|
1262
|
+
{ kind: "semantic-key", value: directGuard.expression },
|
|
1263
|
+
],
|
|
1264
|
+
confidence: "high",
|
|
1265
|
+
support: [
|
|
1266
|
+
{
|
|
1267
|
+
location: guardLocation,
|
|
1268
|
+
role: "direct positive boolean guard at exact surface invocation",
|
|
1269
|
+
},
|
|
1270
|
+
],
|
|
1271
|
+
data: {
|
|
1272
|
+
ownerKey: instanceRef,
|
|
1273
|
+
expressionAnchor: directGuard.expression,
|
|
1274
|
+
plainLanguage: `满足条件:${directGuard.expression}`,
|
|
1275
|
+
valueKind: "runtime",
|
|
1276
|
+
},
|
|
1277
|
+
relatedFactRefs: [instanceRef],
|
|
1278
|
+
});
|
|
1279
|
+
state.registerConditionalSurfaceState(
|
|
1280
|
+
directGuard.stateSymbol,
|
|
1281
|
+
instanceRef,
|
|
1282
|
+
"boolean-true",
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
for (const [
|
|
1286
|
+
stateSymbol,
|
|
1287
|
+
surfaces,
|
|
1288
|
+
] of state.conditionalSurfacesByStateSymbol) {
|
|
1289
|
+
const activation = surfaces.get(templateRef);
|
|
1290
|
+
if (activation) {
|
|
1291
|
+
state.registerConditionalSurfaceState(
|
|
1292
|
+
stateSymbol,
|
|
1293
|
+
instanceRef,
|
|
1294
|
+
activation,
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
instances.set(templateRef, instanceRef);
|
|
1299
|
+
}
|
|
1300
|
+
return instances;
|
|
1301
|
+
}
|
|
1302
|
+
function reachesAny(adjacency, root, targets) {
|
|
1303
|
+
const stateKey = (state) =>
|
|
1304
|
+
[state.callbackKey, state.callSiteStack.join("\u0000")].join("\u0001");
|
|
1305
|
+
const initial = {
|
|
1306
|
+
callbackKey: root,
|
|
1307
|
+
callbackPath: [root],
|
|
1308
|
+
callSiteStack: [],
|
|
1309
|
+
};
|
|
1310
|
+
const visited = new Set([stateKey(initial)]);
|
|
1311
|
+
const pending = [initial];
|
|
1312
|
+
while (pending.length > 0) {
|
|
1313
|
+
const current = pending.shift();
|
|
1314
|
+
if (targets.has(current.callbackKey)) return true;
|
|
1315
|
+
const steps = adjacency.get(current.callbackKey) ?? [];
|
|
1316
|
+
const reverseArgumentCandidateCount = new Set(
|
|
1317
|
+
steps.filter(isCallSiteArgumentStep).map((step) => step.callbackKey),
|
|
1318
|
+
).size;
|
|
1319
|
+
for (const step of steps) {
|
|
1320
|
+
if (current.callbackPath.includes(step.callbackKey)) continue;
|
|
1321
|
+
const transition = advanceCallSiteStack(
|
|
1322
|
+
current.callSiteStack,
|
|
1323
|
+
step,
|
|
1324
|
+
reverseArgumentCandidateCount,
|
|
1325
|
+
);
|
|
1326
|
+
if (!transition.accepted) continue;
|
|
1327
|
+
const next = {
|
|
1328
|
+
callbackKey: step.callbackKey,
|
|
1329
|
+
callbackPath: [...current.callbackPath, step.callbackKey],
|
|
1330
|
+
callSiteStack: transition.callSiteStack,
|
|
1331
|
+
};
|
|
1332
|
+
const key = stateKey(next);
|
|
1333
|
+
if (visited.has(key)) continue;
|
|
1334
|
+
visited.add(key);
|
|
1335
|
+
pending.push(next);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
return false;
|
|
1339
|
+
}
|
|
1340
|
+
function formalCallbacks(callbacks, target, parameter) {
|
|
1341
|
+
const symbols = new Set([
|
|
1342
|
+
`${target.qualifiedName}#parameter:${parameter}`,
|
|
1343
|
+
`${target.signature}#parameter:${parameter}`,
|
|
1344
|
+
]);
|
|
1345
|
+
return new Set(
|
|
1346
|
+
callbacks.flatMap((callback) =>
|
|
1347
|
+
callback.data.parameterName === parameter &&
|
|
1348
|
+
symbols.has(callback.data.callableSymbol)
|
|
1349
|
+
? [callback.id]
|
|
1350
|
+
: [],
|
|
1351
|
+
),
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
function hasFunctionalFormalParameter(target) {
|
|
1355
|
+
const parameters = Array.isArray(target.record.data.parameters)
|
|
1356
|
+
? target.record.data.parameters
|
|
1357
|
+
: [];
|
|
1358
|
+
return parameters.some((value) => {
|
|
1359
|
+
const parameter = object(value);
|
|
1360
|
+
const type = typeof parameter?.type === "string" ? parameter.type : "";
|
|
1361
|
+
return /(?:kotlin\.jvm\.functions\.Function|->|java\.util\.function\.)/u.test(
|
|
1362
|
+
type,
|
|
1363
|
+
);
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
function enclosingComposableForCall(index, call) {
|
|
1367
|
+
const visited = new Set();
|
|
1368
|
+
let current = text(call, "enclosingCallableRef");
|
|
1369
|
+
while (current && !visited.has(current)) {
|
|
1370
|
+
visited.add(current);
|
|
1371
|
+
const method = index.methodsByCallable.get(current);
|
|
1372
|
+
if (method) return method;
|
|
1373
|
+
current = index.callableParent.get(current);
|
|
1374
|
+
}
|
|
1375
|
+
return undefined;
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* A self-contained reusable Composable can own an internal action without an
|
|
1379
|
+
* `onX` formal parameter (for example, an icon that reads shared state and
|
|
1380
|
+
* opens a drawer). When that declaration is invoked from several pages, its
|
|
1381
|
+
* declaration component has several parents and cannot itself be assigned to
|
|
1382
|
+
* one Area. Clone only statically named, directly-owned action controls onto
|
|
1383
|
+
* each exact caller. Reusing the internal callback is correct because its
|
|
1384
|
+
* implementation and effect are declaration-invariant; the call record is
|
|
1385
|
+
* used solely to prove the visual owner for each instance.
|
|
1386
|
+
*/
|
|
1387
|
+
function specializeSelfContainedReusableControls(
|
|
1388
|
+
state,
|
|
1389
|
+
index,
|
|
1390
|
+
inlineVisibilityGuards,
|
|
1391
|
+
) {
|
|
1392
|
+
const elements = state.builder
|
|
1393
|
+
.facts("ui-element")
|
|
1394
|
+
.filter((fact) => fact.kind === "ui-element");
|
|
1395
|
+
const interactions = state.builder
|
|
1396
|
+
.facts("interaction-binding")
|
|
1397
|
+
.filter((fact) => fact.kind === "interaction-binding");
|
|
1398
|
+
const properties = state.builder
|
|
1399
|
+
.facts("ui-property")
|
|
1400
|
+
.filter((fact) => fact.kind === "ui-property");
|
|
1401
|
+
const conditions = state.builder
|
|
1402
|
+
.facts("condition")
|
|
1403
|
+
.filter((fact) => fact.kind === "condition");
|
|
1404
|
+
const callsByTarget = new Map();
|
|
1405
|
+
for (const call of index.calls) {
|
|
1406
|
+
const target = targetForCall(index, call);
|
|
1407
|
+
if (!target || hasFunctionalFormalParameter(target)) continue;
|
|
1408
|
+
const entry = callsByTarget.get(target.componentRef) ?? {
|
|
1409
|
+
target,
|
|
1410
|
+
calls: [],
|
|
1411
|
+
};
|
|
1412
|
+
entry.calls.push(call);
|
|
1413
|
+
callsByTarget.set(target.componentRef, entry);
|
|
1414
|
+
}
|
|
1415
|
+
for (const { target, calls } of callsByTarget.values()) {
|
|
1416
|
+
if (calls.length < 2) continue;
|
|
1417
|
+
const templates = elements.filter(
|
|
1418
|
+
(element) =>
|
|
1419
|
+
element.data.ownerKey === target.componentRef &&
|
|
1420
|
+
element.data.actionable &&
|
|
1421
|
+
element.data.analysisRole !== "call-site-instance" &&
|
|
1422
|
+
Boolean(
|
|
1423
|
+
element.data.visibleText ??
|
|
1424
|
+
element.data.contentDescription ??
|
|
1425
|
+
element.data.associatedLabel,
|
|
1426
|
+
) &&
|
|
1427
|
+
interactions.some(
|
|
1428
|
+
(interaction) => interaction.data.elementKey === element.id,
|
|
1429
|
+
),
|
|
1430
|
+
);
|
|
1431
|
+
for (const template of templates) {
|
|
1432
|
+
const templateInteractions = interactions.filter(
|
|
1433
|
+
(interaction) => interaction.data.elementKey === template.id,
|
|
1434
|
+
);
|
|
1435
|
+
for (const call of calls) {
|
|
1436
|
+
const caller = enclosingComposableForCall(index, call);
|
|
1437
|
+
const location = recordLocation(
|
|
1438
|
+
call,
|
|
1439
|
+
`compose-self-contained-call-site:${target.signature}:${call.id}`,
|
|
1440
|
+
target.signature,
|
|
1441
|
+
);
|
|
1442
|
+
if (
|
|
1443
|
+
!caller ||
|
|
1444
|
+
caller.componentRef === target.componentRef ||
|
|
1445
|
+
!location
|
|
1446
|
+
) {
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
const elementRef = state.builder.addFact("ui-element", {
|
|
1450
|
+
identity: [
|
|
1451
|
+
{ kind: "component", value: caller.componentRef },
|
|
1452
|
+
{ kind: "symbol", value: target.signature },
|
|
1453
|
+
{
|
|
1454
|
+
kind: "control",
|
|
1455
|
+
value: `self-contained-call-site:${call.id}:${template.id}`,
|
|
1456
|
+
},
|
|
1457
|
+
],
|
|
1458
|
+
confidence: template.confidence,
|
|
1459
|
+
support: [
|
|
1460
|
+
{
|
|
1461
|
+
location,
|
|
1462
|
+
role: "exact invocation of a self-contained reusable Composable",
|
|
1463
|
+
},
|
|
1464
|
+
],
|
|
1465
|
+
data: {
|
|
1466
|
+
...template.data,
|
|
1467
|
+
ownerKey: caller.componentRef,
|
|
1468
|
+
analysisRole: "call-site-instance",
|
|
1469
|
+
},
|
|
1470
|
+
relatedFactRefs: [
|
|
1471
|
+
caller.componentRef,
|
|
1472
|
+
...templateInteractions.map(
|
|
1473
|
+
(interaction) => interaction.data.callbackKey,
|
|
1474
|
+
),
|
|
1475
|
+
],
|
|
1476
|
+
});
|
|
1477
|
+
state.uiOwnerById.set(elementRef, caller.componentRef);
|
|
1478
|
+
copyTemplateSemantics(
|
|
1479
|
+
state,
|
|
1480
|
+
template.id,
|
|
1481
|
+
elementRef,
|
|
1482
|
+
properties,
|
|
1483
|
+
conditions,
|
|
1484
|
+
location,
|
|
1485
|
+
);
|
|
1486
|
+
for (const guard of callSiteVisibilityGuards(
|
|
1487
|
+
index,
|
|
1488
|
+
call,
|
|
1489
|
+
inlineVisibilityGuards,
|
|
1490
|
+
)) {
|
|
1491
|
+
const guardLocation =
|
|
1492
|
+
recordLocation(
|
|
1493
|
+
guard.condition,
|
|
1494
|
+
`compose-self-contained-visibility:${guard.expressionAnchor}`,
|
|
1495
|
+
target.signature,
|
|
1496
|
+
) ?? location;
|
|
1497
|
+
state.builder.addFact("condition", {
|
|
1498
|
+
identity: [
|
|
1499
|
+
{ kind: "control", value: elementRef },
|
|
1500
|
+
{ kind: "semantic-key", value: "visibility" },
|
|
1501
|
+
{ kind: "semantic-key", value: guard.expressionAnchor },
|
|
1502
|
+
],
|
|
1503
|
+
confidence: "high",
|
|
1504
|
+
support: [
|
|
1505
|
+
{
|
|
1506
|
+
location: guardLocation,
|
|
1507
|
+
role: "structured call-site visibility guard",
|
|
1508
|
+
},
|
|
1509
|
+
],
|
|
1510
|
+
data: {
|
|
1511
|
+
ownerKey: elementRef,
|
|
1512
|
+
expressionAnchor: guard.expressionAnchor,
|
|
1513
|
+
plainLanguage: guard.plainLanguage,
|
|
1514
|
+
valueKind: "runtime",
|
|
1515
|
+
conditionRole: "visibility",
|
|
1516
|
+
pathRequirement: "runtime-availability",
|
|
1517
|
+
},
|
|
1518
|
+
relatedFactRefs: [elementRef],
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
for (const interaction of templateInteractions) {
|
|
1522
|
+
state.builder.addFact("interaction-binding", {
|
|
1523
|
+
identity: [
|
|
1524
|
+
{ kind: "control", value: elementRef },
|
|
1525
|
+
{ kind: "callback", value: interaction.data.callbackKey },
|
|
1526
|
+
{ kind: "semantic-key", value: interaction.data.trigger },
|
|
1527
|
+
],
|
|
1528
|
+
confidence: interaction.confidence,
|
|
1529
|
+
support: [
|
|
1530
|
+
{
|
|
1531
|
+
location,
|
|
1532
|
+
role: "self-contained call-site Compose interaction",
|
|
1533
|
+
},
|
|
1534
|
+
],
|
|
1535
|
+
data: {
|
|
1536
|
+
...interaction.data,
|
|
1537
|
+
elementKey: elementRef,
|
|
1538
|
+
},
|
|
1539
|
+
relatedFactRefs: [elementRef, interaction.data.callbackKey],
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
state.builder.mergeFactData("ui-element", template.id, {
|
|
1544
|
+
analysisRole: "declaration-template",
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Materializes one product control per exact invocation of a reusable local
|
|
1551
|
+
* Composable. A declaration-level control is converted to a provenance-only
|
|
1552
|
+
* template only after an actual callback and a proven implementation path to
|
|
1553
|
+
* the matching formal callback both exist.
|
|
1554
|
+
*/
|
|
1555
|
+
export function normalizeComposeCallSiteFacts(android, jvm, state) {
|
|
1556
|
+
if (!jvm) return;
|
|
1557
|
+
const index = buildIndex(android, jvm, state);
|
|
1558
|
+
const repeatedCallSites = dynamicRepeatedCallSiteContexts(jvm);
|
|
1559
|
+
const inlineVisibilityGuards = animatedVisibilityGuardsByCall(index);
|
|
1560
|
+
specializeSelfContainedReusableControls(state, index, inlineVisibilityGuards);
|
|
1561
|
+
const callbacks = state.builder
|
|
1562
|
+
.facts("callback")
|
|
1563
|
+
.filter((fact) => fact.kind === "callback");
|
|
1564
|
+
const flows = state.builder
|
|
1565
|
+
.facts("callback-flow")
|
|
1566
|
+
.filter((fact) => fact.kind === "callback-flow");
|
|
1567
|
+
const surfaceTypes = new Set([
|
|
1568
|
+
"dialog",
|
|
1569
|
+
"drawer",
|
|
1570
|
+
"bottom-sheet",
|
|
1571
|
+
"overlay",
|
|
1572
|
+
"popup",
|
|
1573
|
+
"inline-detail-surface",
|
|
1574
|
+
]);
|
|
1575
|
+
const specializedSurfaceTemplates = new Set();
|
|
1576
|
+
const adjacency = executionAdjacency(flows, index, callbacks, state);
|
|
1577
|
+
// Pass 1 materializes reusable child controls inside declaration-level
|
|
1578
|
+
// surfaces. Pass 2 can then clone that exact child set when the reusable
|
|
1579
|
+
// surface itself is invoked. Stable fact identities make the ordinary
|
|
1580
|
+
// controls observed in both passes idempotent.
|
|
1581
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
1582
|
+
const uiElements = state.builder
|
|
1583
|
+
.facts("ui-element")
|
|
1584
|
+
.filter((fact) => fact.kind === "ui-element");
|
|
1585
|
+
const interactions = state.builder
|
|
1586
|
+
.facts("interaction-binding")
|
|
1587
|
+
.filter((fact) => fact.kind === "interaction-binding");
|
|
1588
|
+
const properties = state.builder
|
|
1589
|
+
.facts("ui-property")
|
|
1590
|
+
.filter((fact) => fact.kind === "ui-property");
|
|
1591
|
+
const conditions = state.builder
|
|
1592
|
+
.facts("condition")
|
|
1593
|
+
.filter((fact) => fact.kind === "condition");
|
|
1594
|
+
const uiById = new Map(uiElements.map((fact) => [fact.id, fact]));
|
|
1595
|
+
const componentById = new Map(
|
|
1596
|
+
state.builder
|
|
1597
|
+
.facts("component")
|
|
1598
|
+
.filter((fact) => fact.kind === "component")
|
|
1599
|
+
.map((fact) => [fact.id, fact]),
|
|
1600
|
+
);
|
|
1601
|
+
const surfaceHostByComponent = new Map();
|
|
1602
|
+
for (const binding of state.builder
|
|
1603
|
+
.facts("container-binding")
|
|
1604
|
+
.filter((fact) => fact.kind === "container-binding")) {
|
|
1605
|
+
if (binding.data.bindingKind !== "surface") continue;
|
|
1606
|
+
for (const contentRef of binding.data.contentKeys) {
|
|
1607
|
+
surfaceHostByComponent.set(contentRef, binding.data.hostKey);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
for (const call of index.calls) {
|
|
1611
|
+
if (scopeIsWithinPreview(index, text(call, "enclosingCallableRef")))
|
|
1612
|
+
continue;
|
|
1613
|
+
const target = targetForCall(index, call);
|
|
1614
|
+
if (!target) continue;
|
|
1615
|
+
const repeatedCallSite = repeatedCallSites.get(call.id);
|
|
1616
|
+
if (repeatedCallSite?.population === "impossible") continue;
|
|
1617
|
+
const location = recordLocation(
|
|
1618
|
+
call,
|
|
1619
|
+
`compose-call-site-control:${target.signature}:${call.id}`,
|
|
1620
|
+
target.signature,
|
|
1621
|
+
);
|
|
1622
|
+
if (!location) continue;
|
|
1623
|
+
const components = reachableComponents(index, target);
|
|
1624
|
+
const actions = [];
|
|
1625
|
+
for (const actualBinding of bindingsFor(index, call.id)) {
|
|
1626
|
+
const name = parameterName(actualBinding);
|
|
1627
|
+
if (!name || !/^on[A-Z][A-Za-z0-9]*$/u.test(name)) continue;
|
|
1628
|
+
const actualCallback = concreteCallback(state, index, actualBinding);
|
|
1629
|
+
if (!actualCallback) continue;
|
|
1630
|
+
const actualFact = state.builder.fact(actualCallback);
|
|
1631
|
+
if (!actualFact || actualFact.kind !== "callback") continue;
|
|
1632
|
+
const formals = formalCallbacks(callbacks, target, name);
|
|
1633
|
+
if (formals.size === 0) continue;
|
|
1634
|
+
const matchingTemplates = interactions.filter((interaction) => {
|
|
1635
|
+
const element = uiById.get(interaction.data.elementKey);
|
|
1636
|
+
const surfaceHost = element
|
|
1637
|
+
? surfaceHostByComponent.get(element.data.ownerKey)
|
|
1638
|
+
: undefined;
|
|
1639
|
+
const elementOwner = element
|
|
1640
|
+
? componentById.get(element.data.ownerKey)
|
|
1641
|
+
: undefined;
|
|
1642
|
+
const elementIsSurfaceOwned =
|
|
1643
|
+
elementOwner !== undefined &&
|
|
1644
|
+
surfaceTypes.has(elementOwner.data.componentType);
|
|
1645
|
+
const directlyOwnedSurfaceControl =
|
|
1646
|
+
elementIsSurfaceOwned && surfaceHost === target.componentRef;
|
|
1647
|
+
const eligibleTemplate =
|
|
1648
|
+
element !== undefined &&
|
|
1649
|
+
(element.data.analysisRole === "call-site-instance"
|
|
1650
|
+
? // Pass 2 may use a child control materialized inside this exact
|
|
1651
|
+
// reusable surface, but never a final instance from its caller.
|
|
1652
|
+
directlyOwnedSurfaceControl
|
|
1653
|
+
: directlyOwnedSurfaceControl ||
|
|
1654
|
+
(!elementIsSurfaceOwned &&
|
|
1655
|
+
components.has(element.data.ownerKey)));
|
|
1656
|
+
return (
|
|
1657
|
+
eligibleTemplate &&
|
|
1658
|
+
reachesAny(adjacency, interaction.data.callbackKey, formals)
|
|
1659
|
+
);
|
|
1660
|
+
});
|
|
1661
|
+
const directSurfaceTemplates = matchingTemplates.filter(
|
|
1662
|
+
(interaction) => {
|
|
1663
|
+
const element = uiById.get(interaction.data.elementKey);
|
|
1664
|
+
return (
|
|
1665
|
+
element !== undefined &&
|
|
1666
|
+
surfaceTypes.has(
|
|
1667
|
+
componentById.get(element.data.ownerKey)?.data.componentType ??
|
|
1668
|
+
"other",
|
|
1669
|
+
) &&
|
|
1670
|
+
surfaceHostByComponent.get(element.data.ownerKey) ===
|
|
1671
|
+
target.componentRef
|
|
1672
|
+
);
|
|
1673
|
+
},
|
|
1674
|
+
);
|
|
1675
|
+
// Once exact children have been materialized within the surface, the
|
|
1676
|
+
// lower-level declaration controls are only implementation templates.
|
|
1677
|
+
const templates =
|
|
1678
|
+
directSurfaceTemplates.length > 0
|
|
1679
|
+
? directSurfaceTemplates
|
|
1680
|
+
: matchingTemplates;
|
|
1681
|
+
actions.push(
|
|
1682
|
+
...templates.map((templateBinding) => {
|
|
1683
|
+
const template = uiById.get(templateBinding.data.elementKey);
|
|
1684
|
+
const templateOwner = componentById.get(template.data.ownerKey);
|
|
1685
|
+
const surfaceTemplateRef =
|
|
1686
|
+
templateOwner &&
|
|
1687
|
+
surfaceTypes.has(templateOwner.data.componentType)
|
|
1688
|
+
? template.data.ownerKey
|
|
1689
|
+
: undefined;
|
|
1690
|
+
const instanceOwnerRef =
|
|
1691
|
+
surfaceTemplateRef ?? actualFact.data.ownerKey;
|
|
1692
|
+
return {
|
|
1693
|
+
parameterName: name,
|
|
1694
|
+
actualCallback,
|
|
1695
|
+
actualFact,
|
|
1696
|
+
instanceOwnerRef,
|
|
1697
|
+
...(surfaceTemplateRef ? { surfaceTemplateRef } : {}),
|
|
1698
|
+
templateBinding,
|
|
1699
|
+
template,
|
|
1700
|
+
};
|
|
1701
|
+
}),
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
const mostSpecificActions = actions.filter(
|
|
1705
|
+
(action) =>
|
|
1706
|
+
action.surfaceTemplateRef !== undefined ||
|
|
1707
|
+
!hasMoreSpecificFiniteInvocation(
|
|
1708
|
+
index,
|
|
1709
|
+
target,
|
|
1710
|
+
action.template.data.ownerKey,
|
|
1711
|
+
),
|
|
1712
|
+
);
|
|
1713
|
+
/*
|
|
1714
|
+
* A callback parameter is an event slot, not a control identity. Group
|
|
1715
|
+
* every event that reaches the same declaration-level UI element before
|
|
1716
|
+
* materialising the call-site instance. Distinct template elements stay
|
|
1717
|
+
* distinct even when they share one callback; one combinedClickable or
|
|
1718
|
+
* slider stays one element even when it exposes multiple event slots.
|
|
1719
|
+
*/
|
|
1720
|
+
const surfaceInstances = specializeCallSiteSurfaces(
|
|
1721
|
+
state,
|
|
1722
|
+
index,
|
|
1723
|
+
call,
|
|
1724
|
+
target,
|
|
1725
|
+
mostSpecificActions,
|
|
1726
|
+
new Set(
|
|
1727
|
+
[...surfaceHostByComponent]
|
|
1728
|
+
.filter(([, hostRef]) => hostRef === target.componentRef)
|
|
1729
|
+
.map(([surfaceRef]) => surfaceRef),
|
|
1730
|
+
).size,
|
|
1731
|
+
componentById,
|
|
1732
|
+
properties,
|
|
1733
|
+
conditions,
|
|
1734
|
+
location,
|
|
1735
|
+
);
|
|
1736
|
+
const materializedActions = mostSpecificActions.map((action) => ({
|
|
1737
|
+
...action,
|
|
1738
|
+
instanceOwnerRef: action.surfaceTemplateRef
|
|
1739
|
+
? (surfaceInstances.get(action.surfaceTemplateRef) ??
|
|
1740
|
+
action.instanceOwnerRef)
|
|
1741
|
+
: action.instanceOwnerRef,
|
|
1742
|
+
}));
|
|
1743
|
+
for (const templateRef of surfaceInstances.keys()) {
|
|
1744
|
+
specializedSurfaceTemplates.add(templateRef);
|
|
1745
|
+
}
|
|
1746
|
+
const groupedActions = new Map();
|
|
1747
|
+
for (const action of materializedActions) {
|
|
1748
|
+
const key = `${action.instanceOwnerRef}\u0000${action.template.id}`;
|
|
1749
|
+
const grouped = groupedActions.get(key) ?? [];
|
|
1750
|
+
grouped.push(action);
|
|
1751
|
+
groupedActions.set(key, grouped);
|
|
1752
|
+
}
|
|
1753
|
+
const groups = [...groupedActions.values()].sort(
|
|
1754
|
+
(left, right) =>
|
|
1755
|
+
left[0].template.id.localeCompare(right[0].template.id, "en") ||
|
|
1756
|
+
left[0].instanceOwnerRef.localeCompare(
|
|
1757
|
+
right[0].instanceOwnerRef,
|
|
1758
|
+
"en",
|
|
1759
|
+
),
|
|
1760
|
+
);
|
|
1761
|
+
const ordinaryLabelIsSafe = groups.length === 1;
|
|
1762
|
+
for (const group of groups) {
|
|
1763
|
+
const { actualFact, instanceOwnerRef, template } = group[0];
|
|
1764
|
+
const fallbackNaming =
|
|
1765
|
+
group.length === 1
|
|
1766
|
+
? callSiteNaming(
|
|
1767
|
+
index,
|
|
1768
|
+
call,
|
|
1769
|
+
group[0].parameterName,
|
|
1770
|
+
ordinaryLabelIsSafe,
|
|
1771
|
+
)
|
|
1772
|
+
: ordinaryLabelIsSafe
|
|
1773
|
+
? callSiteNaming(index, call, "__sharedControl", true)
|
|
1774
|
+
: { support: [] };
|
|
1775
|
+
const finiteVariants =
|
|
1776
|
+
group.length === 1
|
|
1777
|
+
? finiteCallSiteNamingVariants(index, call)
|
|
1778
|
+
: undefined;
|
|
1779
|
+
const namingVariants = finiteVariants ?? [fallbackNaming];
|
|
1780
|
+
for (const [variantIndex, naming] of namingVariants.entries()) {
|
|
1781
|
+
const instanceVisibleText =
|
|
1782
|
+
naming.visibleText ?? template.data.visibleText;
|
|
1783
|
+
const instanceContentDescription =
|
|
1784
|
+
naming.contentDescription ?? template.data.contentDescription;
|
|
1785
|
+
const instanceAssociatedLabel =
|
|
1786
|
+
naming.associatedLabel ?? template.data.associatedLabel;
|
|
1787
|
+
const semanticName =
|
|
1788
|
+
instanceVisibleText ??
|
|
1789
|
+
instanceContentDescription ??
|
|
1790
|
+
instanceAssociatedLabel;
|
|
1791
|
+
const elementRef = state.builder.addFact("ui-element", {
|
|
1792
|
+
identity: [
|
|
1793
|
+
{ kind: "component", value: instanceOwnerRef },
|
|
1794
|
+
{ kind: "symbol", value: target.signature },
|
|
1795
|
+
{
|
|
1796
|
+
kind: "control",
|
|
1797
|
+
value:
|
|
1798
|
+
`call-site:${call.id}:${template.id}` +
|
|
1799
|
+
(namingVariants.length > 1
|
|
1800
|
+
? `:finite:${variantIndex}:${instanceVisibleText ?? ""}`
|
|
1801
|
+
: ""),
|
|
1802
|
+
},
|
|
1803
|
+
],
|
|
1804
|
+
confidence: semanticName ? "high" : template.confidence,
|
|
1805
|
+
support: [
|
|
1806
|
+
{
|
|
1807
|
+
location,
|
|
1808
|
+
role: "exact local @Composable control invocation",
|
|
1809
|
+
},
|
|
1810
|
+
...naming.support.flatMap((record) => {
|
|
1811
|
+
const supportLocation = recordLocation(
|
|
1812
|
+
record,
|
|
1813
|
+
`compose-call-site-label:${record.id}`,
|
|
1814
|
+
target.signature,
|
|
1815
|
+
);
|
|
1816
|
+
return supportLocation
|
|
1817
|
+
? [
|
|
1818
|
+
{
|
|
1819
|
+
location: supportLocation,
|
|
1820
|
+
role: "statically resolved call-site naming argument",
|
|
1821
|
+
},
|
|
1822
|
+
]
|
|
1823
|
+
: [];
|
|
1824
|
+
}),
|
|
1825
|
+
...(repeatedCallSite?.support ?? []).flatMap((record) => {
|
|
1826
|
+
const repeatedLocation = recordLocation(
|
|
1827
|
+
record,
|
|
1828
|
+
`compose-call-site-repeated:${record.id}`,
|
|
1829
|
+
target.signature,
|
|
1830
|
+
);
|
|
1831
|
+
return repeatedLocation
|
|
1832
|
+
? [
|
|
1833
|
+
{
|
|
1834
|
+
location: repeatedLocation,
|
|
1835
|
+
role: "resolved Kotlin collection iteration provenance",
|
|
1836
|
+
},
|
|
1837
|
+
]
|
|
1838
|
+
: [];
|
|
1839
|
+
}),
|
|
1840
|
+
],
|
|
1841
|
+
data: {
|
|
1842
|
+
ownerKey: instanceOwnerRef,
|
|
1843
|
+
elementType: repeatedCallSite
|
|
1844
|
+
? "list-item"
|
|
1845
|
+
: template.data.elementType,
|
|
1846
|
+
actionable: template.data.actionable,
|
|
1847
|
+
analysisRole: "call-site-instance",
|
|
1848
|
+
...(template.data.resourceId
|
|
1849
|
+
? { resourceId: template.data.resourceId }
|
|
1850
|
+
: {}),
|
|
1851
|
+
...(template.data.symbol ? { symbol: template.data.symbol } : {}),
|
|
1852
|
+
...(instanceVisibleText
|
|
1853
|
+
? { visibleText: instanceVisibleText }
|
|
1854
|
+
: {}),
|
|
1855
|
+
...(instanceContentDescription
|
|
1856
|
+
? { contentDescription: instanceContentDescription }
|
|
1857
|
+
: {}),
|
|
1858
|
+
...(instanceAssociatedLabel
|
|
1859
|
+
? { associatedLabel: instanceAssociatedLabel }
|
|
1860
|
+
: {}),
|
|
1861
|
+
...(template.data.iconResource
|
|
1862
|
+
? { iconResource: template.data.iconResource }
|
|
1863
|
+
: {}),
|
|
1864
|
+
...(!semanticName && template.data.dynamicIdentity
|
|
1865
|
+
? { dynamicIdentity: template.data.dynamicIdentity }
|
|
1866
|
+
: {}),
|
|
1867
|
+
},
|
|
1868
|
+
relatedFactRefs: [
|
|
1869
|
+
instanceOwnerRef,
|
|
1870
|
+
...new Set(group.map((action) => action.actualCallback)),
|
|
1871
|
+
],
|
|
1872
|
+
});
|
|
1873
|
+
state.uiOwnerById.set(elementRef, instanceOwnerRef);
|
|
1874
|
+
copyTemplateSemantics(
|
|
1875
|
+
state,
|
|
1876
|
+
template.id,
|
|
1877
|
+
elementRef,
|
|
1878
|
+
properties,
|
|
1879
|
+
conditions,
|
|
1880
|
+
location,
|
|
1881
|
+
);
|
|
1882
|
+
for (const guard of callSiteVisibilityGuards(
|
|
1883
|
+
index,
|
|
1884
|
+
call,
|
|
1885
|
+
inlineVisibilityGuards,
|
|
1886
|
+
)) {
|
|
1887
|
+
if (
|
|
1888
|
+
state.builder
|
|
1889
|
+
.facts("condition")
|
|
1890
|
+
.some(
|
|
1891
|
+
(condition) =>
|
|
1892
|
+
condition.kind === "condition" &&
|
|
1893
|
+
condition.data.ownerKey === elementRef &&
|
|
1894
|
+
condition.data.conditionRole === "visibility" &&
|
|
1895
|
+
condition.data.expressionAnchor === guard.expressionAnchor,
|
|
1896
|
+
)
|
|
1897
|
+
)
|
|
1898
|
+
continue;
|
|
1899
|
+
const guardLocation =
|
|
1900
|
+
recordLocation(
|
|
1901
|
+
guard.condition,
|
|
1902
|
+
`compose-call-site-control-visibility:${guard.expressionAnchor}`,
|
|
1903
|
+
target.signature,
|
|
1904
|
+
) ?? location;
|
|
1905
|
+
state.builder.addFact("condition", {
|
|
1906
|
+
identity: [
|
|
1907
|
+
{ kind: "control", value: elementRef },
|
|
1908
|
+
{ kind: "semantic-key", value: "visibility" },
|
|
1909
|
+
{ kind: "semantic-key", value: guard.expressionAnchor },
|
|
1910
|
+
],
|
|
1911
|
+
confidence: "high",
|
|
1912
|
+
support: [
|
|
1913
|
+
{
|
|
1914
|
+
location: guardLocation,
|
|
1915
|
+
role: "structured branch guarding exact local @Composable control invocation",
|
|
1916
|
+
},
|
|
1917
|
+
],
|
|
1918
|
+
data: {
|
|
1919
|
+
ownerKey: elementRef,
|
|
1920
|
+
expressionAnchor: guard.expressionAnchor,
|
|
1921
|
+
plainLanguage: guard.plainLanguage,
|
|
1922
|
+
valueKind: "runtime",
|
|
1923
|
+
conditionRole: "visibility",
|
|
1924
|
+
},
|
|
1925
|
+
relatedFactRefs: [elementRef],
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
for (const availability of repeatedCallSite?.availabilities ?? []) {
|
|
1929
|
+
const availabilityLocation =
|
|
1930
|
+
recordLocation(
|
|
1931
|
+
availability.record,
|
|
1932
|
+
`compose-call-site-runtime-availability:` +
|
|
1933
|
+
availability.expressionAnchor,
|
|
1934
|
+
target.signature,
|
|
1935
|
+
) ?? location;
|
|
1936
|
+
state.builder.addFact("condition", {
|
|
1937
|
+
identity: [
|
|
1938
|
+
{ kind: "control", value: elementRef },
|
|
1939
|
+
{ kind: "semantic-key", value: "visibility" },
|
|
1940
|
+
{
|
|
1941
|
+
kind: "semantic-key",
|
|
1942
|
+
value: availability.expressionAnchor,
|
|
1943
|
+
},
|
|
1944
|
+
],
|
|
1945
|
+
confidence: "high",
|
|
1946
|
+
support: [
|
|
1947
|
+
{
|
|
1948
|
+
location: availabilityLocation,
|
|
1949
|
+
role: "resolved runtime collection-item availability",
|
|
1950
|
+
},
|
|
1951
|
+
],
|
|
1952
|
+
data: {
|
|
1953
|
+
ownerKey: elementRef,
|
|
1954
|
+
expressionAnchor: availability.expressionAnchor,
|
|
1955
|
+
plainLanguage: availability.plainLanguage,
|
|
1956
|
+
valueKind: "runtime",
|
|
1957
|
+
conditionRole: "visibility",
|
|
1958
|
+
pathRequirement: "runtime-availability",
|
|
1959
|
+
},
|
|
1960
|
+
relatedFactRefs: [elementRef],
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
for (const action of group) {
|
|
1964
|
+
state.builder.addFact("interaction-binding", {
|
|
1965
|
+
identity: [
|
|
1966
|
+
{ kind: "control", value: elementRef },
|
|
1967
|
+
{ kind: "callback", value: action.actualCallback },
|
|
1968
|
+
{
|
|
1969
|
+
kind: "semantic-key",
|
|
1970
|
+
value: action.templateBinding.data.trigger,
|
|
1971
|
+
},
|
|
1972
|
+
],
|
|
1973
|
+
confidence: action.templateBinding.confidence,
|
|
1974
|
+
support: [
|
|
1975
|
+
{
|
|
1976
|
+
location,
|
|
1977
|
+
role: "call-site-specialized Compose interaction",
|
|
1978
|
+
},
|
|
1979
|
+
],
|
|
1980
|
+
data: {
|
|
1981
|
+
elementKey: elementRef,
|
|
1982
|
+
callbackKey: action.actualCallback,
|
|
1983
|
+
trigger: action.templateBinding.data.trigger,
|
|
1984
|
+
bindingApi: action.templateBinding.data.bindingApi,
|
|
1985
|
+
},
|
|
1986
|
+
relatedFactRefs: [elementRef, action.actualCallback],
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
state.builder.mergeFactData("ui-element", template.id, {
|
|
1990
|
+
analysisRole: "declaration-template",
|
|
1991
|
+
});
|
|
1992
|
+
state.builder.removeUnresolvedOwnedBy(
|
|
1993
|
+
template.id,
|
|
1994
|
+
new Set([
|
|
1995
|
+
"INTRINSIC_CONTROL_NAME_UNKNOWN",
|
|
1996
|
+
"INTRINSIC_COMPOSE_CONTROL_NAME_UNKNOWN",
|
|
1997
|
+
]),
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
for (const surfaces of state.conditionalSurfacesByStateSymbol.values()) {
|
|
2004
|
+
for (const templateRef of specializedSurfaceTemplates) {
|
|
2005
|
+
surfaces.delete(templateRef);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
}
|