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