@ryanstark24/sfgraph-core 1.2.0 → 1.6.0
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/dist/analyze/apex-platform.d.ts +23 -0
- package/dist/analyze/apex-platform.d.ts.map +1 -0
- package/dist/analyze/apex-platform.js +112 -0
- package/dist/analyze/apex-platform.js.map +1 -0
- package/dist/analyze/audit-graph.d.ts +51 -3
- package/dist/analyze/audit-graph.d.ts.map +1 -1
- package/dist/analyze/audit-graph.js +382 -14
- package/dist/analyze/audit-graph.js.map +1 -1
- package/dist/analyze/component-scope.d.ts +82 -0
- package/dist/analyze/component-scope.d.ts.map +1 -0
- package/dist/analyze/component-scope.js +207 -0
- package/dist/analyze/component-scope.js.map +1 -0
- package/dist/analyze/containment.d.ts +91 -0
- package/dist/analyze/containment.d.ts.map +1 -0
- package/dist/analyze/containment.js +175 -0
- package/dist/analyze/containment.js.map +1 -0
- package/dist/analyze/cross-org.d.ts +35 -0
- package/dist/analyze/cross-org.d.ts.map +1 -1
- package/dist/analyze/cross-org.js +102 -3
- package/dist/analyze/cross-org.js.map +1 -1
- package/dist/analyze/dead-code.d.ts +99 -3
- package/dist/analyze/dead-code.d.ts.map +1 -1
- package/dist/analyze/dead-code.js +282 -10
- package/dist/analyze/dead-code.js.map +1 -1
- package/dist/analyze/dependencies.d.ts +21 -2
- package/dist/analyze/dependencies.d.ts.map +1 -1
- package/dist/analyze/dependencies.js +102 -12
- package/dist/analyze/dependencies.js.map +1 -1
- package/dist/analyze/dependents.d.ts +116 -1
- package/dist/analyze/dependents.d.ts.map +1 -1
- package/dist/analyze/dependents.js +285 -13
- package/dist/analyze/dependents.js.map +1 -1
- package/dist/analyze/deployment-manifest/generate.d.ts +24 -0
- package/dist/analyze/deployment-manifest/generate.d.ts.map +1 -1
- package/dist/analyze/deployment-manifest/generate.js +40 -12
- package/dist/analyze/deployment-manifest/generate.js.map +1 -1
- package/dist/analyze/deployment-manifest/member-name-formatters.d.ts.map +1 -1
- package/dist/analyze/deployment-manifest/member-name-formatters.js +1 -0
- package/dist/analyze/deployment-manifest/member-name-formatters.js.map +1 -1
- package/dist/analyze/deployment-manifest/member-types.d.ts.map +1 -1
- package/dist/analyze/deployment-manifest/member-types.js +2 -0
- package/dist/analyze/deployment-manifest/member-types.js.map +1 -1
- package/dist/analyze/diff-seeds.d.ts +33 -0
- package/dist/analyze/diff-seeds.d.ts.map +1 -0
- package/dist/analyze/diff-seeds.js +299 -0
- package/dist/analyze/diff-seeds.js.map +1 -0
- package/dist/analyze/edge-filters.d.ts +62 -0
- package/dist/analyze/edge-filters.d.ts.map +1 -0
- package/dist/analyze/edge-filters.js +127 -0
- package/dist/analyze/edge-filters.js.map +1 -0
- package/dist/analyze/field-scope.d.ts +25 -0
- package/dist/analyze/field-scope.d.ts.map +1 -0
- package/dist/analyze/field-scope.js +94 -0
- package/dist/analyze/field-scope.js.map +1 -0
- package/dist/analyze/find-nodes.d.ts +48 -12
- package/dist/analyze/find-nodes.d.ts.map +1 -1
- package/dist/analyze/find-nodes.js +323 -12
- package/dist/analyze/find-nodes.js.map +1 -1
- package/dist/analyze/findings.d.ts +1 -1
- package/dist/analyze/findings.d.ts.map +1 -1
- package/dist/analyze/findings.js +15 -3
- package/dist/analyze/findings.js.map +1 -1
- package/dist/analyze/git-diff.d.ts +27 -1
- package/dist/analyze/git-diff.d.ts.map +1 -1
- package/dist/analyze/git-diff.js +198 -26
- package/dist/analyze/git-diff.js.map +1 -1
- package/dist/analyze/governor.d.ts +25 -4
- package/dist/analyze/governor.d.ts.map +1 -1
- package/dist/analyze/governor.js +82 -18
- package/dist/analyze/governor.js.map +1 -1
- package/dist/analyze/index.d.ts +6 -0
- package/dist/analyze/index.d.ts.map +1 -1
- package/dist/analyze/index.js +6 -0
- package/dist/analyze/index.js.map +1 -1
- package/dist/analyze/path-to-qname.d.ts +9 -0
- package/dist/analyze/path-to-qname.d.ts.map +1 -1
- package/dist/analyze/path-to-qname.js +58 -15
- package/dist/analyze/path-to-qname.js.map +1 -1
- package/dist/analyze/populate.d.ts +40 -0
- package/dist/analyze/populate.d.ts.map +1 -1
- package/dist/analyze/populate.js +203 -58
- package/dist/analyze/populate.js.map +1 -1
- package/dist/analyze/pseudo-endpoints.d.ts +67 -0
- package/dist/analyze/pseudo-endpoints.d.ts.map +1 -0
- package/dist/analyze/pseudo-endpoints.js +78 -0
- package/dist/analyze/pseudo-endpoints.js.map +1 -0
- package/dist/analyze/security.d.ts +17 -0
- package/dist/analyze/security.d.ts.map +1 -1
- package/dist/analyze/security.js +103 -18
- package/dist/analyze/security.js.map +1 -1
- package/dist/analyze/test-coverage.d.ts +48 -0
- package/dist/analyze/test-coverage.d.ts.map +1 -1
- package/dist/analyze/test-coverage.js +72 -1
- package/dist/analyze/test-coverage.js.map +1 -1
- package/dist/analyze/wip-changed-files.d.ts +88 -0
- package/dist/analyze/wip-changed-files.d.ts.map +1 -0
- package/dist/analyze/wip-changed-files.js +422 -0
- package/dist/analyze/wip-changed-files.js.map +1 -0
- package/dist/analyze/wip-impact.d.ts +59 -0
- package/dist/analyze/wip-impact.d.ts.map +1 -1
- package/dist/analyze/wip-impact.js +199 -116
- package/dist/analyze/wip-impact.js.map +1 -1
- package/dist/domain/apex-platform.d.ts +10 -0
- package/dist/domain/apex-platform.d.ts.map +1 -0
- package/dist/domain/apex-platform.js +231 -0
- package/dist/domain/apex-platform.js.map +1 -0
- package/dist/domain/index.d.ts +2 -0
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +2 -0
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/metadata-category.d.ts +1 -0
- package/dist/domain/metadata-category.d.ts.map +1 -1
- package/dist/domain/metadata-category.js +1 -0
- package/dist/domain/metadata-category.js.map +1 -1
- package/dist/domain/rel-types.d.ts +7 -0
- package/dist/domain/rel-types.d.ts.map +1 -1
- package/dist/domain/rel-types.js +7 -0
- package/dist/domain/rel-types.js.map +1 -1
- package/dist/domain/standard-sobjects.d.ts +20 -0
- package/dist/domain/standard-sobjects.d.ts.map +1 -0
- package/dist/domain/standard-sobjects.js +183 -0
- package/dist/domain/standard-sobjects.js.map +1 -0
- package/dist/embedding/cache.d.ts +41 -0
- package/dist/embedding/cache.d.ts.map +1 -0
- package/dist/embedding/cache.js +69 -0
- package/dist/embedding/cache.js.map +1 -0
- package/dist/embedding/embed.d.ts +59 -0
- package/dist/embedding/embed.d.ts.map +1 -1
- package/dist/embedding/embed.js +207 -29
- package/dist/embedding/embed.js.map +1 -1
- package/dist/embedding/index.d.ts +7 -0
- package/dist/embedding/index.d.ts.map +1 -1
- package/dist/embedding/index.js +4 -0
- package/dist/embedding/index.js.map +1 -1
- package/dist/embedding/pool.d.ts +66 -6
- package/dist/embedding/pool.d.ts.map +1 -1
- package/dist/embedding/pool.js +156 -6
- package/dist/embedding/pool.js.map +1 -1
- package/dist/embedding/queue.d.ts +166 -9
- package/dist/embedding/queue.d.ts.map +1 -1
- package/dist/embedding/queue.js +397 -39
- package/dist/embedding/queue.js.map +1 -1
- package/dist/embedding/tokenize.d.ts +18 -0
- package/dist/embedding/tokenize.d.ts.map +1 -0
- package/dist/embedding/tokenize.js +43 -0
- package/dist/embedding/tokenize.js.map +1 -0
- package/dist/embedding/worker.d.ts +0 -5
- package/dist/embedding/worker.d.ts.map +1 -1
- package/dist/embedding/worker.js +27 -4
- package/dist/embedding/worker.js.map +1 -1
- package/dist/extractors/filesystem/index.d.ts +21 -1
- package/dist/extractors/filesystem/index.d.ts.map +1 -1
- package/dist/extractors/filesystem/index.js +195 -38
- package/dist/extractors/filesystem/index.js.map +1 -1
- package/dist/extractors/filesystem/vlocity-datapacks.d.ts +27 -0
- package/dist/extractors/filesystem/vlocity-datapacks.d.ts.map +1 -0
- package/dist/extractors/filesystem/vlocity-datapacks.js +201 -0
- package/dist/extractors/filesystem/vlocity-datapacks.js.map +1 -0
- package/dist/extractors/interfaces/metadata-source.d.ts +40 -0
- package/dist/extractors/interfaces/metadata-source.d.ts.map +1 -1
- package/dist/extractors/live-org/bulk-retrieve.d.ts +206 -1
- package/dist/extractors/live-org/bulk-retrieve.d.ts.map +1 -1
- package/dist/extractors/live-org/bulk-retrieve.js +706 -94
- package/dist/extractors/live-org/bulk-retrieve.js.map +1 -1
- package/dist/extractors/live-org/capabilities.d.ts +15 -0
- package/dist/extractors/live-org/capabilities.d.ts.map +1 -1
- package/dist/extractors/live-org/capabilities.js +54 -9
- package/dist/extractors/live-org/capabilities.js.map +1 -1
- package/dist/extractors/live-org/extractors/apex.d.ts +12 -2
- package/dist/extractors/live-org/extractors/apex.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/apex.js +171 -75
- package/dist/extractors/live-org/extractors/apex.js.map +1 -1
- package/dist/extractors/live-org/extractors/custom-settings.d.ts +4 -0
- package/dist/extractors/live-org/extractors/custom-settings.d.ts.map +1 -0
- package/dist/extractors/live-org/extractors/custom-settings.js +76 -0
- package/dist/extractors/live-org/extractors/custom-settings.js.map +1 -0
- package/dist/extractors/live-org/extractors/flow.d.ts +2 -1
- package/dist/extractors/live-org/extractors/flow.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/flow.js +12 -3
- package/dist/extractors/live-org/extractors/flow.js.map +1 -1
- package/dist/extractors/live-org/extractors/generic-metadata.d.ts +23 -3
- package/dist/extractors/live-org/extractors/generic-metadata.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/generic-metadata.js +110 -25
- package/dist/extractors/live-org/extractors/generic-metadata.js.map +1 -1
- package/dist/extractors/live-org/extractors/generic-sobject.js.map +1 -1
- package/dist/extractors/live-org/extractors/integration.d.ts +2 -1
- package/dist/extractors/live-org/extractors/integration.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/integration.js +14 -5
- package/dist/extractors/live-org/extractors/integration.js.map +1 -1
- package/dist/extractors/live-org/extractors/lwc.d.ts +2 -1
- package/dist/extractors/live-org/extractors/lwc.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/lwc.js +95 -47
- package/dist/extractors/live-org/extractors/lwc.js.map +1 -1
- package/dist/extractors/live-org/extractors/mcd-baseline.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/mcd-baseline.js +2 -4
- package/dist/extractors/live-org/extractors/mcd-baseline.js.map +1 -1
- package/dist/extractors/live-org/extractors/object.d.ts +2 -11
- package/dist/extractors/live-org/extractors/object.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/object.js +127 -226
- package/dist/extractors/live-org/extractors/object.js.map +1 -1
- package/dist/extractors/live-org/extractors/omnistudio-retrieve.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/omnistudio-retrieve.js.map +1 -1
- package/dist/extractors/live-org/extractors/omnistudio.d.ts +11 -1
- package/dist/extractors/live-org/extractors/omnistudio.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/omnistudio.js +104 -16
- package/dist/extractors/live-org/extractors/omnistudio.js.map +1 -1
- package/dist/extractors/live-org/extractors/security.d.ts +12 -1
- package/dist/extractors/live-org/extractors/security.d.ts.map +1 -1
- package/dist/extractors/live-org/extractors/security.js +29 -7
- package/dist/extractors/live-org/extractors/security.js.map +1 -1
- package/dist/extractors/live-org/rate-limit.d.ts +48 -16
- package/dist/extractors/live-org/rate-limit.d.ts.map +1 -1
- package/dist/extractors/live-org/rate-limit.js +235 -40
- package/dist/extractors/live-org/rate-limit.js.map +1 -1
- package/dist/extractors/live-org/retry.d.ts +136 -0
- package/dist/extractors/live-org/retry.d.ts.map +1 -0
- package/dist/extractors/live-org/retry.js +416 -0
- package/dist/extractors/live-org/retry.js.map +1 -0
- package/dist/extractors/live-org/vlocity/runner.d.ts +2 -1
- package/dist/extractors/live-org/vlocity/runner.d.ts.map +1 -1
- package/dist/extractors/live-org/vlocity/runner.js +124 -72
- package/dist/extractors/live-org/vlocity/runner.js.map +1 -1
- package/dist/extractors/vlocity-shared/row.d.ts +19 -0
- package/dist/extractors/vlocity-shared/row.d.ts.map +1 -0
- package/dist/extractors/vlocity-shared/row.js +69 -0
- package/dist/extractors/vlocity-shared/row.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/ingest/delta.d.ts +70 -0
- package/dist/ingest/delta.d.ts.map +1 -0
- package/dist/ingest/delta.js +534 -0
- package/dist/ingest/delta.js.map +1 -0
- package/dist/ingest/detect-deletions-guard.d.ts +79 -0
- package/dist/ingest/detect-deletions-guard.d.ts.map +1 -0
- package/dist/ingest/detect-deletions-guard.js +122 -0
- package/dist/ingest/detect-deletions-guard.js.map +1 -0
- package/dist/ingest/index.d.ts +8 -1
- package/dist/ingest/index.d.ts.map +1 -1
- package/dist/ingest/index.js +4 -0
- package/dist/ingest/index.js.map +1 -1
- package/dist/ingest/live-ingest.d.ts +180 -0
- package/dist/ingest/live-ingest.d.ts.map +1 -1
- package/dist/ingest/live-ingest.js +1617 -625
- package/dist/ingest/live-ingest.js.map +1 -1
- package/dist/ingest/lock.d.ts +47 -0
- package/dist/ingest/lock.d.ts.map +1 -0
- package/dist/ingest/lock.js +222 -0
- package/dist/ingest/lock.js.map +1 -0
- package/dist/ingest/member-rows.d.ts +88 -0
- package/dist/ingest/member-rows.d.ts.map +1 -0
- package/dist/ingest/member-rows.js +152 -0
- package/dist/ingest/member-rows.js.map +1 -0
- package/dist/ingest/offline-org.d.ts +8 -0
- package/dist/ingest/offline-org.d.ts.map +1 -0
- package/dist/ingest/offline-org.js +41 -0
- package/dist/ingest/offline-org.js.map +1 -0
- package/dist/ingest/parse-member.d.ts +53 -0
- package/dist/ingest/parse-member.d.ts.map +1 -0
- package/dist/ingest/parse-member.js +276 -0
- package/dist/ingest/parse-member.js.map +1 -0
- package/dist/ingest/parse-pool.d.ts +139 -0
- package/dist/ingest/parse-pool.d.ts.map +1 -0
- package/dist/ingest/parse-pool.js +593 -0
- package/dist/ingest/parse-pool.js.map +1 -0
- package/dist/ingest/parse-worker.d.ts +3 -0
- package/dist/ingest/parse-worker.d.ts.map +1 -0
- package/dist/ingest/parse-worker.js +100 -0
- package/dist/ingest/parse-worker.js.map +1 -0
- package/dist/ingest/profile.d.ts +27 -0
- package/dist/ingest/profile.d.ts.map +1 -0
- package/dist/ingest/profile.js +48 -0
- package/dist/ingest/profile.js.map +1 -0
- package/dist/ingest/resolver-gates.d.ts +12 -0
- package/dist/ingest/resolver-gates.d.ts.map +1 -0
- package/dist/ingest/resolver-gates.js +53 -0
- package/dist/ingest/resolver-gates.js.map +1 -0
- package/dist/parsers/apex/arity-resolver.d.ts +16 -3
- package/dist/parsers/apex/arity-resolver.d.ts.map +1 -1
- package/dist/parsers/apex/arity-resolver.js +31 -2
- package/dist/parsers/apex/arity-resolver.js.map +1 -1
- package/dist/parsers/apex/ast-extractor.d.ts +4 -0
- package/dist/parsers/apex/ast-extractor.d.ts.map +1 -1
- package/dist/parsers/apex/ast-extractor.js +128 -29
- package/dist/parsers/apex/ast-extractor.js.map +1 -1
- package/dist/parsers/apex/class-refs.d.ts +78 -0
- package/dist/parsers/apex/class-refs.d.ts.map +1 -0
- package/dist/parsers/apex/class-refs.js +465 -0
- package/dist/parsers/apex/class-refs.js.map +1 -0
- package/dist/parsers/apex/class.d.ts +158 -0
- package/dist/parsers/apex/class.d.ts.map +1 -1
- package/dist/parsers/apex/class.js +1073 -172
- package/dist/parsers/apex/class.js.map +1 -1
- package/dist/parsers/apex/dynamic-soql.d.ts +52 -0
- package/dist/parsers/apex/dynamic-soql.d.ts.map +1 -0
- package/dist/parsers/apex/dynamic-soql.js +427 -0
- package/dist/parsers/apex/dynamic-soql.js.map +1 -0
- package/dist/parsers/apex/field-access.d.ts +65 -0
- package/dist/parsers/apex/field-access.d.ts.map +1 -0
- package/dist/parsers/apex/field-access.js +438 -0
- package/dist/parsers/apex/field-access.js.map +1 -0
- package/dist/parsers/apex/interface-ref-resolver.d.ts +32 -0
- package/dist/parsers/apex/interface-ref-resolver.d.ts.map +1 -0
- package/dist/parsers/apex/interface-ref-resolver.js +64 -0
- package/dist/parsers/apex/interface-ref-resolver.js.map +1 -0
- package/dist/parsers/apex/name-refs.d.ts +83 -0
- package/dist/parsers/apex/name-refs.d.ts.map +1 -0
- package/dist/parsers/apex/name-refs.js +465 -0
- package/dist/parsers/apex/name-refs.js.map +1 -0
- package/dist/parsers/apex/page-refs.d.ts +11 -0
- package/dist/parsers/apex/page-refs.d.ts.map +1 -0
- package/dist/parsers/apex/page-refs.js +34 -0
- package/dist/parsers/apex/page-refs.js.map +1 -0
- package/dist/parsers/apex/ref-resolver.d.ts +58 -0
- package/dist/parsers/apex/ref-resolver.d.ts.map +1 -0
- package/dist/parsers/apex/ref-resolver.js +348 -0
- package/dist/parsers/apex/ref-resolver.js.map +1 -0
- package/dist/parsers/apex/soql-fields.d.ts +75 -0
- package/dist/parsers/apex/soql-fields.d.ts.map +1 -0
- package/dist/parsers/apex/soql-fields.js +338 -0
- package/dist/parsers/apex/soql-fields.js.map +1 -0
- package/dist/parsers/apex/trigger.d.ts.map +1 -1
- package/dist/parsers/apex/trigger.js +100 -2
- package/dist/parsers/apex/trigger.js.map +1 -1
- package/dist/parsers/apex/vlocity-calls.d.ts +25 -0
- package/dist/parsers/apex/vlocity-calls.d.ts.map +1 -0
- package/dist/parsers/apex/vlocity-calls.js +225 -0
- package/dist/parsers/apex/vlocity-calls.js.map +1 -0
- package/dist/parsers/aura/bundle.d.ts +39 -0
- package/dist/parsers/aura/bundle.d.ts.map +1 -0
- package/dist/parsers/aura/bundle.js +269 -0
- package/dist/parsers/aura/bundle.js.map +1 -0
- package/dist/parsers/aura/embed-resolver.d.ts +17 -0
- package/dist/parsers/aura/embed-resolver.d.ts.map +1 -0
- package/dist/parsers/aura/embed-resolver.js +58 -0
- package/dist/parsers/aura/embed-resolver.js.map +1 -0
- package/dist/parsers/aura/index.d.ts +4 -0
- package/dist/parsers/aura/index.d.ts.map +1 -0
- package/dist/parsers/aura/index.js +6 -0
- package/dist/parsers/aura/index.js.map +1 -0
- package/dist/parsers/common.d.ts +9 -0
- package/dist/parsers/common.d.ts.map +1 -1
- package/dist/parsers/common.js +17 -0
- package/dist/parsers/common.js.map +1 -1
- package/dist/parsers/cross-flavor-resolver.js +8 -2
- package/dist/parsers/cross-flavor-resolver.js.map +1 -1
- package/dist/parsers/flow/invocable-resolver.d.ts.map +1 -1
- package/dist/parsers/flow/invocable-resolver.js +2 -0
- package/dist/parsers/flow/invocable-resolver.js.map +1 -1
- package/dist/parsers/flow/parser.d.ts +9 -0
- package/dist/parsers/flow/parser.d.ts.map +1 -1
- package/dist/parsers/flow/parser.js +327 -2
- package/dist/parsers/flow/parser.js.map +1 -1
- package/dist/parsers/flow/relationship-resolver.d.ts +34 -0
- package/dist/parsers/flow/relationship-resolver.d.ts.map +1 -0
- package/dist/parsers/flow/relationship-resolver.js +199 -0
- package/dist/parsers/flow/relationship-resolver.js.map +1 -0
- package/dist/parsers/generic/opaque-node-parser.d.ts +5 -1
- package/dist/parsers/generic/opaque-node-parser.d.ts.map +1 -1
- package/dist/parsers/generic/opaque-node-parser.js +37 -5
- package/dist/parsers/generic/opaque-node-parser.js.map +1 -1
- package/dist/parsers/generic/reflection-walker.d.ts +7 -0
- package/dist/parsers/generic/reflection-walker.d.ts.map +1 -1
- package/dist/parsers/generic/reflection-walker.js +38 -13
- package/dist/parsers/generic/reflection-walker.js.map +1 -1
- package/dist/parsers/hash-salts.d.ts +72 -0
- package/dist/parsers/hash-salts.d.ts.map +1 -0
- package/dist/parsers/hash-salts.js +91 -0
- package/dist/parsers/hash-salts.js.map +1 -0
- package/dist/parsers/index.d.ts +2 -0
- package/dist/parsers/index.d.ts.map +1 -1
- package/dist/parsers/index.js +2 -0
- package/dist/parsers/index.js.map +1 -1
- package/dist/parsers/lwc/bundle.d.ts +1 -0
- package/dist/parsers/lwc/bundle.d.ts.map +1 -1
- package/dist/parsers/lwc/bundle.js +37 -2
- package/dist/parsers/lwc/bundle.js.map +1 -1
- package/dist/parsers/lwc/field-key-resolver.d.ts +32 -0
- package/dist/parsers/lwc/field-key-resolver.d.ts.map +1 -0
- package/dist/parsers/lwc/field-key-resolver.js +92 -0
- package/dist/parsers/lwc/field-key-resolver.js.map +1 -0
- package/dist/parsers/lwc/html-visitor.d.ts +1 -1
- package/dist/parsers/lwc/html-visitor.d.ts.map +1 -1
- package/dist/parsers/lwc/html-visitor.js +81 -3
- package/dist/parsers/lwc/html-visitor.js.map +1 -1
- package/dist/parsers/lwc/js-visitor.d.ts +16 -0
- package/dist/parsers/lwc/js-visitor.d.ts.map +1 -1
- package/dist/parsers/lwc/js-visitor.js +354 -12
- package/dist/parsers/lwc/js-visitor.js.map +1 -1
- package/dist/parsers/lwc/omnidef.d.ts +10 -0
- package/dist/parsers/lwc/omnidef.d.ts.map +1 -0
- package/dist/parsers/lwc/omnidef.js +127 -0
- package/dist/parsers/lwc/omnidef.js.map +1 -0
- package/dist/parsers/lwc/xml-meta.d.ts.map +1 -1
- package/dist/parsers/lwc/xml-meta.js +16 -1
- package/dist/parsers/lwc/xml-meta.js.map +1 -1
- package/dist/parsers/object/formula-hop-resolver.d.ts +33 -0
- package/dist/parsers/object/formula-hop-resolver.d.ts.map +1 -0
- package/dist/parsers/object/formula-hop-resolver.js +60 -0
- package/dist/parsers/object/formula-hop-resolver.js.map +1 -0
- package/dist/parsers/object/formula-refs.d.ts +40 -0
- package/dist/parsers/object/formula-refs.d.ts.map +1 -0
- package/dist/parsers/object/formula-refs.js +129 -0
- package/dist/parsers/object/formula-refs.js.map +1 -0
- package/dist/parsers/object/object.d.ts +3 -0
- package/dist/parsers/object/object.d.ts.map +1 -1
- package/dist/parsers/object/object.js +54 -57
- package/dist/parsers/object/object.js.map +1 -1
- package/dist/parsers/omnistudio/data-transform.d.ts.map +1 -1
- package/dist/parsers/omnistudio/data-transform.js +40 -7
- package/dist/parsers/omnistudio/data-transform.js.map +1 -1
- package/dist/parsers/omnistudio/integration-procedure.d.ts.map +1 -1
- package/dist/parsers/omnistudio/integration-procedure.js +6 -6
- package/dist/parsers/omnistudio/integration-procedure.js.map +1 -1
- package/dist/parsers/omnistudio/overlap-detector.d.ts.map +1 -1
- package/dist/parsers/omnistudio/overlap-detector.js +29 -11
- package/dist/parsers/omnistudio/overlap-detector.js.map +1 -1
- package/dist/parsers/omnistudio/process.d.ts.map +1 -1
- package/dist/parsers/omnistudio/process.js +6 -6
- package/dist/parsers/omnistudio/process.js.map +1 -1
- package/dist/parsers/omnistudio/remote-refs.d.ts +11 -0
- package/dist/parsers/omnistudio/remote-refs.d.ts.map +1 -0
- package/dist/parsers/omnistudio/remote-refs.js +17 -0
- package/dist/parsers/omnistudio/remote-refs.js.map +1 -0
- package/dist/parsers/omnistudio/ui-card.d.ts.map +1 -1
- package/dist/parsers/omnistudio/ui-card.js +6 -6
- package/dist/parsers/omnistudio/ui-card.js.map +1 -1
- package/dist/parsers/resolver-prune.d.ts +14 -0
- package/dist/parsers/resolver-prune.d.ts.map +1 -0
- package/dist/parsers/resolver-prune.js +29 -0
- package/dist/parsers/resolver-prune.js.map +1 -0
- package/dist/parsers/rules/_engine.d.ts.map +1 -1
- package/dist/parsers/rules/_engine.js +4 -0
- package/dist/parsers/rules/_engine.js.map +1 -1
- package/dist/parsers/rules/_engine.ts +3 -0
- package/dist/parsers/rules/_loader.d.ts +8 -0
- package/dist/parsers/rules/_loader.d.ts.map +1 -1
- package/dist/parsers/rules/_loader.js +17 -0
- package/dist/parsers/rules/_loader.js.map +1 -1
- package/dist/parsers/rules/_loader.ts +19 -0
- package/dist/parsers/rules/_schema.d.ts +12 -12
- package/dist/parsers/rules/_selectors.d.ts +1 -0
- package/dist/parsers/rules/_selectors.d.ts.map +1 -1
- package/dist/parsers/rules/_selectors.js +6 -0
- package/dist/parsers/rules/_selectors.js.map +1 -1
- package/dist/parsers/rules/_selectors.ts +6 -0
- package/dist/parsers/rules/apex-component.yml +49 -4
- package/dist/parsers/rules/apex-page.yml +49 -4
- package/dist/parsers/rules/approval-process.yml +25 -5
- package/dist/parsers/rules/custom-metadata-type.yml +14 -7
- package/dist/parsers/rules/custom-setting.yml +24 -0
- package/dist/parsers/rules/custom-tab.yml +41 -0
- package/dist/parsers/rules/lightning-page.yml +4 -2
- package/dist/parsers/rules/workflow.yml +30 -10
- package/dist/parsers/vf/refs.d.ts +39 -0
- package/dist/parsers/vf/refs.d.ts.map +1 -0
- package/dist/parsers/vf/refs.js +152 -0
- package/dist/parsers/vf/refs.js.map +1 -0
- package/dist/parsers/vlocity/apex-resolver.d.ts +21 -0
- package/dist/parsers/vlocity/apex-resolver.d.ts.map +1 -0
- package/dist/parsers/vlocity/apex-resolver.js +115 -0
- package/dist/parsers/vlocity/apex-resolver.js.map +1 -0
- package/dist/parsers/vlocity/common.d.ts +72 -1
- package/dist/parsers/vlocity/common.d.ts.map +1 -1
- package/dist/parsers/vlocity/common.js +332 -2
- package/dist/parsers/vlocity/common.js.map +1 -1
- package/dist/parsers/vlocity/data-raptor.d.ts +9 -0
- package/dist/parsers/vlocity/data-raptor.d.ts.map +1 -1
- package/dist/parsers/vlocity/data-raptor.js +148 -17
- package/dist/parsers/vlocity/data-raptor.js.map +1 -1
- package/dist/parsers/vlocity/datapack-refs.d.ts +22 -0
- package/dist/parsers/vlocity/datapack-refs.d.ts.map +1 -0
- package/dist/parsers/vlocity/datapack-refs.js +126 -0
- package/dist/parsers/vlocity/datapack-refs.js.map +1 -0
- package/dist/parsers/vlocity/dr-relationship-resolver.d.ts +26 -0
- package/dist/parsers/vlocity/dr-relationship-resolver.d.ts.map +1 -0
- package/dist/parsers/vlocity/dr-relationship-resolver.js +137 -0
- package/dist/parsers/vlocity/dr-relationship-resolver.js.map +1 -0
- package/dist/parsers/vlocity/integration-procedure.d.ts.map +1 -1
- package/dist/parsers/vlocity/integration-procedure.js +15 -48
- package/dist/parsers/vlocity/integration-procedure.js.map +1 -1
- package/dist/parsers/vlocity/name-match-resolver.d.ts +21 -0
- package/dist/parsers/vlocity/name-match-resolver.d.ts.map +1 -0
- package/dist/parsers/vlocity/name-match-resolver.js +97 -0
- package/dist/parsers/vlocity/name-match-resolver.js.map +1 -0
- package/dist/parsers/vlocity/omni-script.d.ts.map +1 -1
- package/dist/parsers/vlocity/omni-script.js +18 -32
- package/dist/parsers/vlocity/omni-script.js.map +1 -1
- package/dist/parsers/vlocity/vlocity-card.d.ts.map +1 -1
- package/dist/parsers/vlocity/vlocity-card.js +30 -36
- package/dist/parsers/vlocity/vlocity-card.js.map +1 -1
- package/dist/render/mermaid/dependency-graph.d.ts.map +1 -1
- package/dist/render/mermaid/dependency-graph.js +13 -1
- package/dist/render/mermaid/dependency-graph.js.map +1 -1
- package/dist/render/mermaid/diff.d.ts.map +1 -1
- package/dist/render/mermaid/diff.js +4 -2
- package/dist/render/mermaid/diff.js.map +1 -1
- package/dist/render/mermaid/shared.d.ts +7 -0
- package/dist/render/mermaid/shared.d.ts.map +1 -1
- package/dist/render/mermaid/shared.js +15 -6
- package/dist/render/mermaid/shared.js.map +1 -1
- package/dist/render/sarif.d.ts.map +1 -1
- package/dist/render/sarif.js +1 -3
- package/dist/render/sarif.js.map +1 -1
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +2 -2
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/interfaces.d.ts +212 -1
- package/dist/storage/interfaces.d.ts.map +1 -1
- package/dist/storage/sqlite/graph-store.d.ts +366 -3
- package/dist/storage/sqlite/graph-store.d.ts.map +1 -1
- package/dist/storage/sqlite/graph-store.js +1755 -68
- package/dist/storage/sqlite/graph-store.js.map +1 -1
- package/dist/storage/sqlite/migrations.d.ts +11 -0
- package/dist/storage/sqlite/migrations.d.ts.map +1 -1
- package/dist/storage/sqlite/migrations.js +245 -11
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/storage/sqlite/rename-stability.d.ts +1 -1
- package/dist/storage/sqlite/rename-stability.d.ts.map +1 -1
- package/dist/storage/sqlite/rename-stability.js +2 -6
- package/dist/storage/sqlite/rename-stability.js.map +1 -1
- package/dist/storage/sqlite/snapshot-store.d.ts +79 -3
- package/dist/storage/sqlite/snapshot-store.d.ts.map +1 -1
- package/dist/storage/sqlite/snapshot-store.js +500 -100
- package/dist/storage/sqlite/snapshot-store.js.map +1 -1
- package/dist/storage/sqlite/snapshot-versions.d.ts +51 -0
- package/dist/storage/sqlite/snapshot-versions.d.ts.map +1 -0
- package/dist/storage/sqlite/snapshot-versions.js +209 -0
- package/dist/storage/sqlite/snapshot-versions.js.map +1 -0
- package/dist/storage/sqlite/vector-store.d.ts +12 -0
- package/dist/storage/sqlite/vector-store.d.ts.map +1 -1
- package/dist/storage/sqlite/vector-store.js +57 -14
- package/dist/storage/sqlite/vector-store.js.map +1 -1
- package/dist/util/process-owner.d.ts +35 -0
- package/dist/util/process-owner.d.ts.map +1 -0
- package/dist/util/process-owner.js +87 -0
- package/dist/util/process-owner.js.map +1 -0
- package/dist/util/worker-heap-cap.d.ts +10 -0
- package/dist/util/worker-heap-cap.d.ts.map +1 -0
- package/dist/util/worker-heap-cap.js +65 -0
- package/dist/util/worker-heap-cap.js.map +1 -0
- package/package.json +6 -7
- package/dist/analyze/__tests__/audit-graph.test.d.ts +0 -2
- package/dist/analyze/__tests__/audit-graph.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/audit-graph.test.js +0 -122
- package/dist/analyze/__tests__/audit-graph.test.js.map +0 -1
- package/dist/analyze/__tests__/cross-org-two-store.test.d.ts +0 -2
- package/dist/analyze/__tests__/cross-org-two-store.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/cross-org-two-store.test.js +0 -68
- package/dist/analyze/__tests__/cross-org-two-store.test.js.map +0 -1
- package/dist/analyze/__tests__/dead-code-confidence.test.d.ts +0 -2
- package/dist/analyze/__tests__/dead-code-confidence.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/dead-code-confidence.test.js +0 -66
- package/dist/analyze/__tests__/dead-code-confidence.test.js.map +0 -1
- package/dist/analyze/__tests__/deployment-manifest.test.d.ts +0 -2
- package/dist/analyze/__tests__/deployment-manifest.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/deployment-manifest.test.js +0 -66
- package/dist/analyze/__tests__/deployment-manifest.test.js.map +0 -1
- package/dist/analyze/__tests__/find-nodes.test.d.ts +0 -2
- package/dist/analyze/__tests__/find-nodes.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/find-nodes.test.js +0 -129
- package/dist/analyze/__tests__/find-nodes.test.js.map +0 -1
- package/dist/analyze/__tests__/findings.test.d.ts +0 -2
- package/dist/analyze/__tests__/findings.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/findings.test.js +0 -90
- package/dist/analyze/__tests__/findings.test.js.map +0 -1
- package/dist/analyze/__tests__/governor-risks-detection.test.d.ts +0 -2
- package/dist/analyze/__tests__/governor-risks-detection.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/governor-risks-detection.test.js +0 -70
- package/dist/analyze/__tests__/governor-risks-detection.test.js.map +0 -1
- package/dist/analyze/__tests__/populate.test.d.ts +0 -2
- package/dist/analyze/__tests__/populate.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/populate.test.js +0 -129
- package/dist/analyze/__tests__/populate.test.js.map +0 -1
- package/dist/analyze/__tests__/traversal-caps.test.d.ts +0 -2
- package/dist/analyze/__tests__/traversal-caps.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/traversal-caps.test.js +0 -129
- package/dist/analyze/__tests__/traversal-caps.test.js.map +0 -1
- package/dist/analyze/__tests__/wip-impact.test.d.ts +0 -2
- package/dist/analyze/__tests__/wip-impact.test.d.ts.map +0 -1
- package/dist/analyze/__tests__/wip-impact.test.js +0 -134
- package/dist/analyze/__tests__/wip-impact.test.js.map +0 -1
- package/dist/embedding/__tests__/embed.test.d.ts +0 -2
- package/dist/embedding/__tests__/embed.test.d.ts.map +0 -1
- package/dist/embedding/__tests__/embed.test.js +0 -72
- package/dist/embedding/__tests__/embed.test.js.map +0 -1
- package/dist/embedding/__tests__/queue.test.d.ts +0 -2
- package/dist/embedding/__tests__/queue.test.d.ts.map +0 -1
- package/dist/embedding/__tests__/queue.test.js +0 -64
- package/dist/embedding/__tests__/queue.test.js.map +0 -1
- package/dist/extractors/__tests__/network-egress.test.d.ts +0 -2
- package/dist/extractors/__tests__/network-egress.test.d.ts.map +0 -1
- package/dist/extractors/__tests__/network-egress.test.js +0 -32
- package/dist/extractors/__tests__/network-egress.test.js.map +0 -1
- package/dist/extractors/filesystem/__tests__/filesystem.test.d.ts +0 -2
- package/dist/extractors/filesystem/__tests__/filesystem.test.d.ts.map +0 -1
- package/dist/extractors/filesystem/__tests__/filesystem.test.js +0 -96
- package/dist/extractors/filesystem/__tests__/filesystem.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/_jsforce-mock.d.ts +0 -38
- package/dist/extractors/live-org/__tests__/_jsforce-mock.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/_jsforce-mock.js +0 -96
- package/dist/extractors/live-org/__tests__/_jsforce-mock.js.map +0 -1
- package/dist/extractors/live-org/__tests__/auth.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/auth.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/auth.test.js +0 -85
- package/dist/extractors/live-org/__tests__/auth.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/bulk-retrieve.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/bulk-retrieve.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/bulk-retrieve.test.js +0 -150
- package/dist/extractors/live-org/__tests__/bulk-retrieve.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/capabilities.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/capabilities.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/capabilities.test.js +0 -54
- package/dist/extractors/live-org/__tests__/capabilities.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/discovery.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/discovery.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/discovery.test.js +0 -55
- package/dist/extractors/live-org/__tests__/discovery.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/dispatch.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/dispatch.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/dispatch.test.js +0 -68
- package/dist/extractors/live-org/__tests__/dispatch.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/extractor-enrichment.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/extractor-enrichment.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/extractor-enrichment.test.js +0 -312
- package/dist/extractors/live-org/__tests__/extractor-enrichment.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/liveness.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/liveness.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/liveness.test.js +0 -148
- package/dist/extractors/live-org/__tests__/liveness.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/mcd-baseline.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/mcd-baseline.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/mcd-baseline.test.js +0 -174
- package/dist/extractors/live-org/__tests__/mcd-baseline.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/metadata-read-batch.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/metadata-read-batch.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/metadata-read-batch.test.js +0 -75
- package/dist/extractors/live-org/__tests__/metadata-read-batch.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.js +0 -135
- package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/pools.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/pools.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/pools.test.js +0 -75
- package/dist/extractors/live-org/__tests__/pools.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/rate-limit.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/rate-limit.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/rate-limit.test.js +0 -78
- package/dist/extractors/live-org/__tests__/rate-limit.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/read-only-proxy.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/read-only-proxy.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/read-only-proxy.test.js +0 -376
- package/dist/extractors/live-org/__tests__/read-only-proxy.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/soql-rebatch.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/soql-rebatch.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/soql-rebatch.test.js +0 -118
- package/dist/extractors/live-org/__tests__/soql-rebatch.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/source-member.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/source-member.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/source-member.test.js +0 -84
- package/dist/extractors/live-org/__tests__/source-member.test.js.map +0 -1
- package/dist/extractors/live-org/__tests__/vlocity-runner.test.d.ts +0 -2
- package/dist/extractors/live-org/__tests__/vlocity-runner.test.d.ts.map +0 -1
- package/dist/extractors/live-org/__tests__/vlocity-runner.test.js +0 -115
- package/dist/extractors/live-org/__tests__/vlocity-runner.test.js.map +0 -1
- package/dist/ingest/__tests__/full-sync-deletion.test.d.ts +0 -2
- package/dist/ingest/__tests__/full-sync-deletion.test.d.ts.map +0 -1
- package/dist/ingest/__tests__/full-sync-deletion.test.js +0 -200
- package/dist/ingest/__tests__/full-sync-deletion.test.js.map +0 -1
- package/dist/ingest/__tests__/incremental-deletion.test.d.ts +0 -2
- package/dist/ingest/__tests__/incremental-deletion.test.d.ts.map +0 -1
- package/dist/ingest/__tests__/incremental-deletion.test.js +0 -92
- package/dist/ingest/__tests__/incremental-deletion.test.js.map +0 -1
- package/dist/ingest/__tests__/live-ingest.test.d.ts +0 -2
- package/dist/ingest/__tests__/live-ingest.test.d.ts.map +0 -1
- package/dist/ingest/__tests__/live-ingest.test.js +0 -155
- package/dist/ingest/__tests__/live-ingest.test.js.map +0 -1
- package/dist/ingest/__tests__/multi-org-orchestrator.test.d.ts +0 -2
- package/dist/ingest/__tests__/multi-org-orchestrator.test.d.ts.map +0 -1
- package/dist/ingest/__tests__/multi-org-orchestrator.test.js +0 -108
- package/dist/ingest/__tests__/multi-org-orchestrator.test.js.map +0 -1
- package/dist/ingest/__tests__/read-only-wrapping.test.d.ts +0 -2
- package/dist/ingest/__tests__/read-only-wrapping.test.d.ts.map +0 -1
- package/dist/ingest/__tests__/read-only-wrapping.test.js +0 -62
- package/dist/ingest/__tests__/read-only-wrapping.test.js.map +0 -1
- package/dist/parsers/__tests__/_harness.d.ts +0 -13
- package/dist/parsers/__tests__/_harness.d.ts.map +0 -1
- package/dist/parsers/__tests__/_harness.js +0 -84
- package/dist/parsers/__tests__/_harness.js.map +0 -1
- package/dist/parsers/__tests__/apex.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/apex.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/apex.golden.test.js +0 -28
- package/dist/parsers/__tests__/apex.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/cross-flavor.test.d.ts +0 -2
- package/dist/parsers/__tests__/cross-flavor.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/cross-flavor.test.js +0 -56
- package/dist/parsers/__tests__/cross-flavor.test.js.map +0 -1
- package/dist/parsers/__tests__/edge-provenance.test.d.ts +0 -2
- package/dist/parsers/__tests__/edge-provenance.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/edge-provenance.test.js +0 -44
- package/dist/parsers/__tests__/edge-provenance.test.js.map +0 -1
- package/dist/parsers/__tests__/flow.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/flow.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/flow.golden.test.js +0 -17
- package/dist/parsers/__tests__/flow.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/integration.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/integration.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/integration.golden.test.js +0 -31
- package/dist/parsers/__tests__/integration.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/long-tail.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/long-tail.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/long-tail.golden.test.js +0 -115
- package/dist/parsers/__tests__/long-tail.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/lwc-bindings.test.d.ts +0 -2
- package/dist/parsers/__tests__/lwc-bindings.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/lwc-bindings.test.js +0 -146
- package/dist/parsers/__tests__/lwc-bindings.test.js.map +0 -1
- package/dist/parsers/__tests__/lwc.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/lwc.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/lwc.golden.test.js +0 -16
- package/dist/parsers/__tests__/lwc.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/mcd-gap-fills.test.d.ts +0 -2
- package/dist/parsers/__tests__/mcd-gap-fills.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/mcd-gap-fills.test.js +0 -75
- package/dist/parsers/__tests__/mcd-gap-fills.test.js.map +0 -1
- package/dist/parsers/__tests__/object-inline-fields.test.d.ts +0 -2
- package/dist/parsers/__tests__/object-inline-fields.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/object-inline-fields.test.js +0 -115
- package/dist/parsers/__tests__/object-inline-fields.test.js.map +0 -1
- package/dist/parsers/__tests__/object.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/object.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/object.golden.test.js +0 -33
- package/dist/parsers/__tests__/object.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/omnistudio-overlap.test.d.ts +0 -2
- package/dist/parsers/__tests__/omnistudio-overlap.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/omnistudio-overlap.test.js +0 -163
- package/dist/parsers/__tests__/omnistudio-overlap.test.js.map +0 -1
- package/dist/parsers/__tests__/omnistudio.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/omnistudio.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/omnistudio.golden.test.js +0 -54
- package/dist/parsers/__tests__/omnistudio.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/reflection-walker.test.d.ts +0 -2
- package/dist/parsers/__tests__/reflection-walker.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/reflection-walker.test.js +0 -162
- package/dist/parsers/__tests__/reflection-walker.test.js.map +0 -1
- package/dist/parsers/__tests__/registry.test.d.ts +0 -2
- package/dist/parsers/__tests__/registry.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/registry.test.js +0 -23
- package/dist/parsers/__tests__/registry.test.js.map +0 -1
- package/dist/parsers/__tests__/security.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/security.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/security.golden.test.js +0 -31
- package/dist/parsers/__tests__/security.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/vlocity.golden.test.d.ts +0 -2
- package/dist/parsers/__tests__/vlocity.golden.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/vlocity.golden.test.js +0 -109
- package/dist/parsers/__tests__/vlocity.golden.test.js.map +0 -1
- package/dist/parsers/__tests__/worker-pool.test.d.ts +0 -2
- package/dist/parsers/__tests__/worker-pool.test.d.ts.map +0 -1
- package/dist/parsers/__tests__/worker-pool.test.js +0 -48
- package/dist/parsers/__tests__/worker-pool.test.js.map +0 -1
- package/dist/parsers/apex/__tests__/arity-resolver.test.d.ts +0 -2
- package/dist/parsers/apex/__tests__/arity-resolver.test.d.ts.map +0 -1
- package/dist/parsers/apex/__tests__/arity-resolver.test.js +0 -135
- package/dist/parsers/apex/__tests__/arity-resolver.test.js.map +0 -1
- package/dist/parsers/apex/__tests__/ast-extractor.test.d.ts +0 -2
- package/dist/parsers/apex/__tests__/ast-extractor.test.d.ts.map +0 -1
- package/dist/parsers/apex/__tests__/ast-extractor.test.js +0 -146
- package/dist/parsers/apex/__tests__/ast-extractor.test.js.map +0 -1
- package/dist/parsers/apex/__tests__/is-test-attribute.test.d.ts +0 -2
- package/dist/parsers/apex/__tests__/is-test-attribute.test.d.ts.map +0 -1
- package/dist/parsers/apex/__tests__/is-test-attribute.test.js +0 -54
- package/dist/parsers/apex/__tests__/is-test-attribute.test.js.map +0 -1
- package/dist/parsers/apex/__tests__/regex-arg-count.test.d.ts +0 -2
- package/dist/parsers/apex/__tests__/regex-arg-count.test.d.ts.map +0 -1
- package/dist/parsers/apex/__tests__/regex-arg-count.test.js +0 -82
- package/dist/parsers/apex/__tests__/regex-arg-count.test.js.map +0 -1
- package/dist/parsers/flow/__tests__/invocable-resolver.test.d.ts +0 -2
- package/dist/parsers/flow/__tests__/invocable-resolver.test.d.ts.map +0 -1
- package/dist/parsers/flow/__tests__/invocable-resolver.test.js +0 -91
- package/dist/parsers/flow/__tests__/invocable-resolver.test.js.map +0 -1
- package/dist/parsers/rules/__tests__/applies-to.test.d.ts +0 -2
- package/dist/parsers/rules/__tests__/applies-to.test.d.ts.map +0 -1
- package/dist/parsers/rules/__tests__/applies-to.test.js +0 -31
- package/dist/parsers/rules/__tests__/applies-to.test.js.map +0 -1
- package/dist/parsers/rules/__tests__/applies-to.test.ts +0 -40
- package/dist/parsers/rules/__tests__/engine.test.d.ts +0 -2
- package/dist/parsers/rules/__tests__/engine.test.d.ts.map +0 -1
- package/dist/parsers/rules/__tests__/engine.test.js +0 -127
- package/dist/parsers/rules/__tests__/engine.test.js.map +0 -1
- package/dist/parsers/rules/__tests__/engine.test.ts +0 -138
- package/dist/parsers/rules/__tests__/loader.test.d.ts +0 -2
- package/dist/parsers/rules/__tests__/loader.test.d.ts.map +0 -1
- package/dist/parsers/rules/__tests__/loader.test.js +0 -31
- package/dist/parsers/rules/__tests__/loader.test.js.map +0 -1
- package/dist/parsers/rules/__tests__/loader.test.ts +0 -37
- package/dist/parsers/rules/__tests__/schema.test.d.ts +0 -2
- package/dist/parsers/rules/__tests__/schema.test.d.ts.map +0 -1
- package/dist/parsers/rules/__tests__/schema.test.js +0 -33
- package/dist/parsers/rules/__tests__/schema.test.js.map +0 -1
- package/dist/parsers/rules/__tests__/schema.test.ts +0 -37
- package/dist/parsers/rules/__tests__/selectors.test.d.ts +0 -2
- package/dist/parsers/rules/__tests__/selectors.test.d.ts.map +0 -1
- package/dist/parsers/rules/__tests__/selectors.test.js +0 -51
- package/dist/parsers/rules/__tests__/selectors.test.js.map +0 -1
- package/dist/parsers/rules/__tests__/selectors.test.ts +0 -62
- package/dist/render/__tests__/sarif.test.d.ts +0 -2
- package/dist/render/__tests__/sarif.test.d.ts.map +0 -1
- package/dist/render/__tests__/sarif.test.js +0 -129
- package/dist/render/__tests__/sarif.test.js.map +0 -1
- package/dist/render/mermaid/__tests__/mermaid.test.d.ts +0 -2
- package/dist/render/mermaid/__tests__/mermaid.test.d.ts.map +0 -1
- package/dist/render/mermaid/__tests__/mermaid.test.js +0 -74
- package/dist/render/mermaid/__tests__/mermaid.test.js.map +0 -1
- package/dist/storage/__tests__/analysis-tables-migration.test.d.ts +0 -2
- package/dist/storage/__tests__/analysis-tables-migration.test.d.ts.map +0 -1
- package/dist/storage/__tests__/analysis-tables-migration.test.js +0 -44
- package/dist/storage/__tests__/analysis-tables-migration.test.js.map +0 -1
- package/dist/storage/__tests__/graph-store.test.d.ts +0 -2
- package/dist/storage/__tests__/graph-store.test.d.ts.map +0 -1
- package/dist/storage/__tests__/graph-store.test.js +0 -132
- package/dist/storage/__tests__/graph-store.test.js.map +0 -1
- package/dist/storage/__tests__/identifier.test.d.ts +0 -2
- package/dist/storage/__tests__/identifier.test.d.ts.map +0 -1
- package/dist/storage/__tests__/identifier.test.js +0 -35
- package/dist/storage/__tests__/identifier.test.js.map +0 -1
- package/dist/storage/__tests__/load-better-sqlite3.test.d.ts +0 -2
- package/dist/storage/__tests__/load-better-sqlite3.test.d.ts.map +0 -1
- package/dist/storage/__tests__/load-better-sqlite3.test.js +0 -65
- package/dist/storage/__tests__/load-better-sqlite3.test.js.map +0 -1
- package/dist/storage/__tests__/migrations.test.d.ts +0 -2
- package/dist/storage/__tests__/migrations.test.d.ts.map +0 -1
- package/dist/storage/__tests__/migrations.test.js +0 -93
- package/dist/storage/__tests__/migrations.test.js.map +0 -1
- package/dist/storage/__tests__/perf-50k.test.d.ts +0 -2
- package/dist/storage/__tests__/perf-50k.test.d.ts.map +0 -1
- package/dist/storage/__tests__/perf-50k.test.js +0 -67
- package/dist/storage/__tests__/perf-50k.test.js.map +0 -1
- package/dist/storage/__tests__/rename-stability.test.d.ts +0 -2
- package/dist/storage/__tests__/rename-stability.test.d.ts.map +0 -1
- package/dist/storage/__tests__/rename-stability.test.js +0 -135
- package/dist/storage/__tests__/rename-stability.test.js.map +0 -1
- package/dist/storage/__tests__/snapshot-store.test.d.ts +0 -2
- package/dist/storage/__tests__/snapshot-store.test.d.ts.map +0 -1
- package/dist/storage/__tests__/snapshot-store.test.js +0 -117
- package/dist/storage/__tests__/snapshot-store.test.js.map +0 -1
- package/dist/storage/__tests__/snippets.test.d.ts +0 -2
- package/dist/storage/__tests__/snippets.test.d.ts.map +0 -1
- package/dist/storage/__tests__/snippets.test.js +0 -88
- package/dist/storage/__tests__/snippets.test.js.map +0 -1
- package/dist/storage/__tests__/vector-store-load-failure.test.d.ts +0 -2
- package/dist/storage/__tests__/vector-store-load-failure.test.d.ts.map +0 -1
- package/dist/storage/__tests__/vector-store-load-failure.test.js +0 -42
- package/dist/storage/__tests__/vector-store-load-failure.test.js.map +0 -1
- package/dist/storage/__tests__/vector-store.test.d.ts +0 -2
- package/dist/storage/__tests__/vector-store.test.d.ts.map +0 -1
- package/dist/storage/__tests__/vector-store.test.js +0 -87
- package/dist/storage/__tests__/vector-store.test.js.map +0 -1
- package/dist/telemetry/__tests__/event-schema.test.d.ts +0 -2
- package/dist/telemetry/__tests__/event-schema.test.d.ts.map +0 -1
- package/dist/telemetry/__tests__/event-schema.test.js +0 -56
- package/dist/telemetry/__tests__/event-schema.test.js.map +0 -1
- package/dist/telemetry/__tests__/machine-id.test.d.ts +0 -2
- package/dist/telemetry/__tests__/machine-id.test.d.ts.map +0 -1
- package/dist/telemetry/__tests__/machine-id.test.js +0 -33
- package/dist/telemetry/__tests__/machine-id.test.js.map +0 -1
- package/dist/telemetry/__tests__/pipeline.test.d.ts +0 -2
- package/dist/telemetry/__tests__/pipeline.test.d.ts.map +0 -1
- package/dist/telemetry/__tests__/pipeline.test.js +0 -68
- package/dist/telemetry/__tests__/pipeline.test.js.map +0 -1
- package/dist/telemetry/__tests__/sanitizer.test.d.ts +0 -2
- package/dist/telemetry/__tests__/sanitizer.test.d.ts.map +0 -1
- package/dist/telemetry/__tests__/sanitizer.test.js +0 -143
- package/dist/telemetry/__tests__/sanitizer.test.js.map +0 -1
|
@@ -2,8 +2,88 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { asOrgId, asQualifiedName, asSha256 } from "@ryanstark24/sfgraph-shared";
|
|
3
3
|
import { METADATA_CATEGORY, REL_TYPES } from "../../domain/index.js";
|
|
4
4
|
import { makeEdge, makeNode, stripNs } from "../common.js";
|
|
5
|
+
import { APEX_NODE_HASH_SALT } from "../hash-salts.js";
|
|
6
|
+
import { NEW_INSTANCE_RE, STATIC_CALL_RE, emitClassRefEdges, emitDescribeRefEdges, emitStaticCallEdges, emitTypeRefEdges, isUserClassName, localVarNames, } from "./class-refs.js";
|
|
5
7
|
import { parseAnnotations, stripCommentsAndStrings } from "./common.js";
|
|
6
|
-
|
|
8
|
+
import { PLACEHOLDER, dynamicSoqlClauseFields, foldDynamicSoql, maskApex, parseDynamicSoql, } from "./dynamic-soql.js";
|
|
9
|
+
import { classScopes, collectDecls, extractFieldAccesses, paramDecls, } from "./field-access.js";
|
|
10
|
+
import { emitInstanceCallEdges, emitNameRefEdges, makeTypeLookup, recordTypeAt, } from "./name-refs.js";
|
|
11
|
+
import { emitPageRefEdges } from "./page-refs.js";
|
|
12
|
+
import { blankBracketGroups, findInlineQueries, soqlFieldRefs } from "./soql-fields.js";
|
|
13
|
+
import { classStringConstants, extractVlocityCalls, extractVlocityDispatch, stripCommentsOnly, } from "./vlocity-calls.js";
|
|
14
|
+
export { APEX_NODE_HASH_SALT } from "../hash-salts.js";
|
|
15
|
+
/**
|
|
16
|
+
* apex-parser (antlr4ts) keeps a DFA cache on each grammar's static ATN. It is
|
|
17
|
+
* per isolate, it is never trimmed, and it grows with every distinct input:
|
|
18
|
+
* over a large Vlocity corpus it held ~190 MB live (and a 2-4x larger V8 heap) in each
|
|
19
|
+
* parse worker. The cache is only a memo for prediction, so dropping it does
|
|
20
|
+
* not change any parse result. Release it after this many syntax checks in the
|
|
21
|
+
* current isolate (each parse worker, or the main thread in serial mode).
|
|
22
|
+
* Measured on a large Vlocity repo, 4 workers: 50 cut peak RSS by ~1.07 GB for ~5% more parse
|
|
23
|
+
* time.
|
|
24
|
+
*/
|
|
25
|
+
export const APEX_DFA_CLEAR_INTERVAL = 50;
|
|
26
|
+
let apexChecksSinceDfaClear = 0;
|
|
27
|
+
/** Count one apex-parser syntax check; clear the lexer and parser DFA when due. */
|
|
28
|
+
function noteApexSyntaxCheck(lexer, parser) {
|
|
29
|
+
apexChecksSinceDfaClear++;
|
|
30
|
+
if (apexChecksSinceDfaClear < APEX_DFA_CLEAR_INTERVAL)
|
|
31
|
+
return;
|
|
32
|
+
apexChecksSinceDfaClear = 0;
|
|
33
|
+
lexer?.interpreter?.clearDFA();
|
|
34
|
+
parser?.interpreter?.clearDFA();
|
|
35
|
+
}
|
|
36
|
+
/** Test hook: restart the DFA-release counter for this isolate. */
|
|
37
|
+
export function resetApexDfaCounterForTest() {
|
|
38
|
+
apexChecksSinceDfaClear = 0;
|
|
39
|
+
}
|
|
40
|
+
// Modifiers are optional (`*`, not `+`): a method with NO explicit access
|
|
41
|
+
// modifier is implicitly private in Apex and is still a real method — the old
|
|
42
|
+
// `+` silently dropped every unmodified helper/inner-class method. Allowing
|
|
43
|
+
// zero modifiers means the regex can also touch control-flow that happens to
|
|
44
|
+
// look like `<word> <word>(...) {` (notably `else if (x) {`), so findMethods
|
|
45
|
+
// rejects any match whose return-type or name is a reserved keyword.
|
|
46
|
+
const METHOD_RE = /(?:@[\w()=,'"\s.]+\s+)*(?:(public|private|protected|global|virtual|override|abstract|static|webservice|with\s+sharing|without\s+sharing|inherited\s+sharing|testmethod|final)\s+)*([A-Za-z_][\w<>,.\s\[\]]*?)\s+([A-Za-z_]\w*)\s*\(([^)]*)\)\s*\{/gi;
|
|
47
|
+
// Apex reserved words that can appear as `<kw> <kw>(...) {` but are NOT method
|
|
48
|
+
// declarations. If the captured return-type or method-name is one of these, the
|
|
49
|
+
// match is control flow / a statement, not a method.
|
|
50
|
+
const NON_METHOD_KEYWORDS = new Set([
|
|
51
|
+
"if",
|
|
52
|
+
"else",
|
|
53
|
+
"for",
|
|
54
|
+
"while",
|
|
55
|
+
"do",
|
|
56
|
+
"switch",
|
|
57
|
+
"catch",
|
|
58
|
+
"try",
|
|
59
|
+
"finally",
|
|
60
|
+
"return",
|
|
61
|
+
"new",
|
|
62
|
+
"throw",
|
|
63
|
+
"when",
|
|
64
|
+
]);
|
|
65
|
+
// Access/sharing/definition modifiers. When the regex makes modifiers optional,
|
|
66
|
+
// a CONSTRUCTOR `public Foo(...) {` backtracks to returnType="public", name="Foo"
|
|
67
|
+
// (the class name) — a phantom method. A real method's return type is a TYPE,
|
|
68
|
+
// never a modifier, so a modifier captured AS the return type means we matched a
|
|
69
|
+
// constructor (or other non-method) and must drop it.
|
|
70
|
+
const MODIFIER_KEYWORDS = new Set([
|
|
71
|
+
"public",
|
|
72
|
+
"private",
|
|
73
|
+
"protected",
|
|
74
|
+
"global",
|
|
75
|
+
"virtual",
|
|
76
|
+
"override",
|
|
77
|
+
"abstract",
|
|
78
|
+
"static",
|
|
79
|
+
"webservice",
|
|
80
|
+
"with",
|
|
81
|
+
"without",
|
|
82
|
+
"inherited",
|
|
83
|
+
"sharing",
|
|
84
|
+
"testmethod",
|
|
85
|
+
"final",
|
|
86
|
+
]);
|
|
7
87
|
function findMatchingBrace(src, openIdx) {
|
|
8
88
|
let depth = 0;
|
|
9
89
|
for (let i = openIdx; i < src.length; i++) {
|
|
@@ -18,48 +98,208 @@ function findMatchingBrace(src, openIdx) {
|
|
|
18
98
|
}
|
|
19
99
|
return -1;
|
|
20
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Split `raw` on commas at generic/bracket/paren depth 0, so
|
|
103
|
+
* `Map<Id, Contact> a, List<String> b` is two items, not three.
|
|
104
|
+
*/
|
|
105
|
+
function splitTopLevel(raw) {
|
|
106
|
+
const out = [];
|
|
107
|
+
let depth = 0;
|
|
108
|
+
let cur = "";
|
|
109
|
+
for (const c of raw) {
|
|
110
|
+
if (c === "<" || c === "(" || c === "[" || c === "{")
|
|
111
|
+
depth++;
|
|
112
|
+
else if ((c === ">" || c === ")" || c === "]" || c === "}") && depth > 0)
|
|
113
|
+
depth--;
|
|
114
|
+
if (c === "," && depth === 0) {
|
|
115
|
+
out.push(cur);
|
|
116
|
+
cur = "";
|
|
117
|
+
}
|
|
118
|
+
else
|
|
119
|
+
cur += c;
|
|
120
|
+
}
|
|
121
|
+
out.push(cur);
|
|
122
|
+
return out.map((x) => x.trim()).filter((x) => x.length > 0);
|
|
123
|
+
}
|
|
21
124
|
function parseParams(raw) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
125
|
+
return splitTopLevel(raw);
|
|
126
|
+
}
|
|
127
|
+
/** `Database.Batchable<SObject>` -> `Database.Batchable` (whitespace-normalised). */
|
|
128
|
+
function stripTypeArgs(t) {
|
|
129
|
+
const lt = t.indexOf("<");
|
|
130
|
+
return (lt >= 0 ? t.slice(0, lt) : t).replace(/\s+/g, "");
|
|
131
|
+
}
|
|
132
|
+
/** Remove `@Name` / `@Name(...)` annotations (balanced parens) from `text`. */
|
|
133
|
+
function stripAnnotations(text) {
|
|
134
|
+
let out = "";
|
|
135
|
+
for (let i = 0; i < text.length; i++) {
|
|
136
|
+
if (text[i] !== "@") {
|
|
137
|
+
out += text[i];
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
let j = i + 1;
|
|
141
|
+
while (j < text.length && /\w/.test(text[j] ?? ""))
|
|
142
|
+
j++;
|
|
143
|
+
let k = j;
|
|
144
|
+
while (k < text.length && /\s/.test(text[k] ?? ""))
|
|
145
|
+
k++;
|
|
146
|
+
if (text[k] === "(") {
|
|
147
|
+
let d = 0;
|
|
148
|
+
for (; k < text.length; k++) {
|
|
149
|
+
if (text[k] === "(")
|
|
150
|
+
d++;
|
|
151
|
+
else if (text[k] === ")" && --d === 0)
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
j = k + 1;
|
|
155
|
+
}
|
|
156
|
+
out += " ";
|
|
157
|
+
i = j - 1;
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
26
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Header of the file's OUTER type: the first `class`/`interface`/`enum` keyword at
|
|
163
|
+
* brace depth 0 (so an inner class can never be taken for the outer one),
|
|
164
|
+
* with `extends` / `implements` clauses parsed by a depth-aware split so
|
|
165
|
+
* generic arguments (`Database.Batchable<SObject>`) neither break the match
|
|
166
|
+
* nor split the list. Type arguments are dropped from the emitted names.
|
|
167
|
+
* `src` must have comments and strings stripped.
|
|
168
|
+
*/
|
|
27
169
|
function extractClassHeader(src) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
170
|
+
const empty = {
|
|
171
|
+
name: "",
|
|
172
|
+
extendsClass: null,
|
|
173
|
+
extendsList: [],
|
|
174
|
+
implementsList: [],
|
|
175
|
+
isInterface: false,
|
|
176
|
+
isTest: false,
|
|
177
|
+
modifiers: [],
|
|
178
|
+
};
|
|
179
|
+
// `enum` too: a top-level enum file declares its modifiers like a class does (cycle 3 T6 fix 1).
|
|
180
|
+
const kwRe = /\b(class|interface|enum)\s+([A-Za-z_]\w*)/gi;
|
|
181
|
+
let depth = 0;
|
|
182
|
+
let scanned = 0;
|
|
183
|
+
let hit = null;
|
|
184
|
+
for (let m = kwRe.exec(src); m !== null; m = kwRe.exec(src)) {
|
|
185
|
+
for (; scanned < m.index; scanned++) {
|
|
186
|
+
if (src[scanned] === "{")
|
|
187
|
+
depth++;
|
|
188
|
+
else if (src[scanned] === "}")
|
|
189
|
+
depth--;
|
|
190
|
+
}
|
|
191
|
+
if (depth === 0) {
|
|
192
|
+
hit = m;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
38
195
|
}
|
|
39
|
-
|
|
196
|
+
if (!hit)
|
|
197
|
+
return empty;
|
|
198
|
+
const open = src.indexOf("{", hit.index + hit[0].length);
|
|
199
|
+
if (open < 0)
|
|
200
|
+
return empty;
|
|
201
|
+
const isInterface = (hit[1] ?? "").toLowerCase() === "interface";
|
|
202
|
+
const name = hit[2] ?? "";
|
|
203
|
+
// Everything before the keyword at depth 0 is annotations + modifiers. Only
|
|
204
|
+
// the trailing statement matters (a preceding `}`/`;` cannot occur before
|
|
205
|
+
// the outer type, but be defensive).
|
|
206
|
+
const prefixStart = Math.max(src.lastIndexOf("}", hit.index), src.lastIndexOf(";", hit.index));
|
|
207
|
+
const prefix = src.slice(prefixStart + 1, hit.index);
|
|
208
|
+
const isTest = parseAnnotations(prefix).some((a) => a.name.toLowerCase() === "istest");
|
|
209
|
+
const modifiers = stripAnnotations(prefix)
|
|
40
210
|
.trim()
|
|
41
211
|
.split(/\s+/)
|
|
42
212
|
.filter((s) => s.length > 0);
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.filter((
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
213
|
+
const tail = src.slice(hit.index + hit[0].length, open);
|
|
214
|
+
const extIdx = tail.search(/\bextends\b/i);
|
|
215
|
+
const implIdx = tail.search(/\bimplements\b/i);
|
|
216
|
+
const clause = (from, kwLen) => {
|
|
217
|
+
if (from < 0)
|
|
218
|
+
return "";
|
|
219
|
+
const ends = [extIdx, implIdx].filter((x) => x > from);
|
|
220
|
+
return tail.slice(from + kwLen, ends.length > 0 ? Math.min(...ends) : tail.length);
|
|
221
|
+
};
|
|
222
|
+
const extendsRaw = clause(extIdx, "extends".length);
|
|
223
|
+
const implementsRaw = clause(implIdx, "implements".length);
|
|
224
|
+
// An interface may extend several interfaces; a class extends one class.
|
|
225
|
+
const extendsList = splitTopLevel(extendsRaw)
|
|
226
|
+
.map(stripTypeArgs)
|
|
227
|
+
.filter((x) => x);
|
|
228
|
+
const implementsList = splitTopLevel(implementsRaw)
|
|
229
|
+
.map(stripTypeArgs)
|
|
230
|
+
.filter((x) => x.length > 0);
|
|
231
|
+
return {
|
|
232
|
+
name,
|
|
233
|
+
extendsClass: extendsList[0] ?? null,
|
|
234
|
+
extendsList,
|
|
235
|
+
implementsList,
|
|
236
|
+
isInterface,
|
|
237
|
+
isTest,
|
|
238
|
+
modifiers,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* The bundled apex-parser grammar predates Apex's null-coalescing operator, so
|
|
243
|
+
* `a ?? b` is a syntax error to it. For grammar validation only, rewrite every
|
|
244
|
+
* code-level `??` (never inside comments or strings) to `||`: same length, so
|
|
245
|
+
* token offsets stay valid, and the same binary-expression shape. The original
|
|
246
|
+
* source is still used for every extraction and snippet.
|
|
247
|
+
*/
|
|
248
|
+
export function rewriteNullCoalescing(src) {
|
|
249
|
+
if (!src.includes("??"))
|
|
250
|
+
return src;
|
|
251
|
+
const masked = maskApex(src);
|
|
252
|
+
const out = src.split("");
|
|
253
|
+
for (let i = masked.indexOf("??"); i >= 0; i = masked.indexOf("??", i + 2)) {
|
|
254
|
+
out[i] = "|";
|
|
255
|
+
out[i + 1] = "|";
|
|
256
|
+
}
|
|
257
|
+
return out.join("");
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Human-readable message for a parse failure. apex-parser's
|
|
261
|
+
* ThrowingErrorListener throws a plain `{ line, column, message }` object (not
|
|
262
|
+
* an Error), which `String(err)` turned into "[object Object]".
|
|
263
|
+
*/
|
|
264
|
+
export function formatParseError(err) {
|
|
265
|
+
const MAX = 300;
|
|
266
|
+
const clip = (s) => (s.length > MAX ? `${s.slice(0, MAX)}…` : s);
|
|
267
|
+
if (err instanceof Error)
|
|
268
|
+
return clip(err.message);
|
|
269
|
+
if (err && typeof err === "object") {
|
|
270
|
+
const e = err;
|
|
271
|
+
const msg = typeof e.message === "string" ? e.message : JSON.stringify(err);
|
|
272
|
+
const pos = typeof e.line === "number" ? `${e.line}:${typeof e.column === "number" ? e.column : 0} ` : "";
|
|
273
|
+
return clip(`${pos}${msg}`);
|
|
274
|
+
}
|
|
275
|
+
return clip(String(err));
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Raw (comment/string-preserving) body of the method's declaration: the first
|
|
279
|
+
* not-yet-used `name(<same arity>) {` in `masked` that isn't a call (`.name(`)
|
|
280
|
+
* or constructor invocation. Matching and brace-balancing run on `masked`; the
|
|
281
|
+
* slice is taken from `src` at the same offsets. Null when not found.
|
|
282
|
+
*/
|
|
283
|
+
function declRawBody(src, masked, m, used) {
|
|
284
|
+
const re = new RegExp(`(?<![.\\w])${m.name}\\s*\\(([^)]*)\\)\\s*\\{`, "g");
|
|
285
|
+
for (const hit of masked.matchAll(re)) {
|
|
286
|
+
const at = hit.index ?? 0;
|
|
287
|
+
if (used.has(at))
|
|
288
|
+
continue;
|
|
289
|
+
if (/\bnew\s+$/.test(masked.slice(Math.max(0, at - 5), at)))
|
|
290
|
+
continue;
|
|
291
|
+
if (parseParams(hit[1] ?? "").length !== m.params.length)
|
|
292
|
+
continue;
|
|
293
|
+
const open = at + hit[0].length - 1;
|
|
294
|
+
const close = findMatchingBrace(masked, open);
|
|
295
|
+
if (close <= 0)
|
|
296
|
+
return null;
|
|
297
|
+
used.add(at);
|
|
298
|
+
return { text: src.slice(open + 1, close), start: open + 1 };
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
function findMethods(src, className) {
|
|
63
303
|
const out = [];
|
|
64
304
|
const re = new RegExp(METHOD_RE.source, "gi");
|
|
65
305
|
let m = re.exec(src);
|
|
@@ -67,6 +307,19 @@ function findMethods(src) {
|
|
|
67
307
|
const returnType = (m[2] ?? "").trim();
|
|
68
308
|
const name = m[3] ?? "";
|
|
69
309
|
const params = parseParams(m[4] ?? "");
|
|
310
|
+
// Reject non-methods that share the `<word> <word>(...) {` shape:
|
|
311
|
+
// - control flow: `else if (x) {` → returnType="else", name="if".
|
|
312
|
+
// - a CONSTRUCTOR: `public Foo(...) {` backtracks to returnType="public"
|
|
313
|
+
// (a modifier) / name=<class name>. A real method's return type is a
|
|
314
|
+
// type, never a modifier, and its name is never the class name.
|
|
315
|
+
if (NON_METHOD_KEYWORDS.has(returnType.toLowerCase()) ||
|
|
316
|
+
NON_METHOD_KEYWORDS.has(name.toLowerCase()) ||
|
|
317
|
+
MODIFIER_KEYWORDS.has(returnType.toLowerCase()) ||
|
|
318
|
+
(className !== undefined && name === className)) {
|
|
319
|
+
re.lastIndex = (m.index ?? 0) + 1;
|
|
320
|
+
m = re.exec(src);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
70
323
|
// Skip constructors (returnType empty or equals class name when first letter is uppercase)
|
|
71
324
|
// We'll keep ctor handling outside.
|
|
72
325
|
const openBraceIdx = (m.index ?? 0) + m[0].length - 1;
|
|
@@ -110,95 +363,507 @@ function extractApiVersion(metaXml) {
|
|
|
110
363
|
const m = metaXml.match(/<apiVersion>([^<]+)<\/apiVersion>/);
|
|
111
364
|
return m?.[1] ? m[1].trim() : null;
|
|
112
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* `<shadowsManaged>ns</shadowsManaged>` entries the live extractor adds to the
|
|
368
|
+
* metaXml envelope when this local class shares its (namespace-less) qname
|
|
369
|
+
* with managed-package classes. Not a Salesforce field.
|
|
370
|
+
*/
|
|
371
|
+
function extractShadowsManaged(metaXml) {
|
|
372
|
+
if (!metaXml)
|
|
373
|
+
return [];
|
|
374
|
+
const out = [];
|
|
375
|
+
for (const m of metaXml.matchAll(/<shadowsManaged>([^<]+)<\/shadowsManaged>/g)) {
|
|
376
|
+
const ns = m[1]?.trim();
|
|
377
|
+
if (ns)
|
|
378
|
+
out.push(ns);
|
|
379
|
+
}
|
|
380
|
+
return out;
|
|
381
|
+
}
|
|
113
382
|
function sha256(s) {
|
|
114
383
|
return createHash("sha256").update(s).digest("hex");
|
|
115
384
|
}
|
|
116
|
-
const SOQL_RE = /\[\s*SELECT\b([^\]]*?)\bFROM\s+([A-Za-z_][\w.]*)/gi;
|
|
117
|
-
const SOSL_RE = /\[\s*FIND\b/gi;
|
|
118
|
-
const DML_RE = /\b(insert|update|delete|upsert|undelete|merge)\s+([A-Za-z_][\w.]*)/gi;
|
|
119
|
-
const FIELD_ACCESS_RE = /\b([A-Z][A-Za-z0-9_]*(?:__r)?)\.([A-Za-z_][\w]*?)(?:__c)?\b/g;
|
|
120
|
-
const NEW_INSTANCE_RE = /\bnew\s+([A-Z][A-Za-z0-9_]*)\s*\(/g;
|
|
121
|
-
const STATIC_CALL_RE = /\b([A-Z][A-Za-z0-9_]*)\.([a-z][A-Za-z0-9_]*)\s*\(/g;
|
|
122
385
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
386
|
+
* Compute the character ranges of loop BODIES (`{ … }` opened by for/while/
|
|
387
|
+
* do) within a method body. Used to stamp `inLoop: true` on SOQL/DML edges in
|
|
388
|
+
* the regex extraction path so governor-risk detection works on the DEFAULT
|
|
389
|
+
* parser mode — not only under SFGRAPH_APEX_PARSER=ast (the AST extractor
|
|
390
|
+
* stamps inLoop separately). The body passed here has strings/comments
|
|
391
|
+
* stripped, so brace counting is reliable.
|
|
128
392
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* args) fall back to `(?)` — preserving the existing ambiguous-fan-out
|
|
134
|
-
* semantics on the long tail.
|
|
393
|
+
* A for-EACH header query — `for (Account a : [SELECT …]) {…}` — runs once and
|
|
394
|
+
* is correctly EXCLUDED: its index falls before the loop body's opening brace,
|
|
395
|
+
* not inside the recorded range. Known limitation (shared with the populate
|
|
396
|
+
* body-scan): a braceless single-statement loop body isn't tracked.
|
|
135
397
|
*/
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
398
|
+
export function computeLoopRanges(body) {
|
|
399
|
+
const ranges = [];
|
|
400
|
+
const stack = [];
|
|
401
|
+
// Brace positions known to open a loop body. Marking the SPECIFIC `{` (rather
|
|
402
|
+
// than a sticky `pendingLoop` flag) is what fixes braceless loops: a
|
|
403
|
+
// `for (...) doIt();` or `do x(); while(y);` has no brace, so the next
|
|
404
|
+
// UNRELATED `{` is no longer mis-marked as a loop body (the old flag bled into
|
|
405
|
+
// it, producing false SOQL/DML-in-loop governor findings).
|
|
406
|
+
const loopBraces = new Set();
|
|
407
|
+
const wordBefore = (i) => /\W/.test(body[i - 1] ?? " ");
|
|
408
|
+
const boundaryAt = (i) => /\W/.test(body[i] ?? " ");
|
|
409
|
+
const skipWs = (i) => {
|
|
410
|
+
let j = i;
|
|
411
|
+
while (j < body.length && /\s/.test(body[j] ?? ""))
|
|
412
|
+
j += 1;
|
|
413
|
+
return j;
|
|
414
|
+
};
|
|
415
|
+
// Index of the `)` matching the `(` at `open`, or -1.
|
|
416
|
+
const matchParen = (open) => {
|
|
417
|
+
let d = 0;
|
|
418
|
+
for (let j = open; j < body.length; j++) {
|
|
419
|
+
if (body[j] === "(")
|
|
420
|
+
d += 1;
|
|
421
|
+
else if (body[j] === ")" && --d === 0)
|
|
422
|
+
return j;
|
|
423
|
+
}
|
|
424
|
+
return -1;
|
|
425
|
+
};
|
|
426
|
+
// End (`;` at depth 0) of the single statement starting at `from`.
|
|
427
|
+
const stmtEnd = (from) => {
|
|
428
|
+
let d = 0;
|
|
429
|
+
for (let j = from; j < body.length; j++) {
|
|
430
|
+
const c = body[j];
|
|
431
|
+
if (c === "(" || c === "{" || c === "[")
|
|
432
|
+
d += 1;
|
|
433
|
+
else if (c === ")" || c === "}" || c === "]")
|
|
434
|
+
d -= 1;
|
|
435
|
+
else if (c === ";" && d === 0)
|
|
436
|
+
return j;
|
|
437
|
+
}
|
|
438
|
+
return body.length - 1;
|
|
439
|
+
};
|
|
440
|
+
for (let i = 0; i < body.length; i++) {
|
|
144
441
|
const ch = body[i];
|
|
145
|
-
|
|
146
|
-
if (ch === "
|
|
147
|
-
|
|
148
|
-
|
|
442
|
+
let kwLen = 0;
|
|
443
|
+
if (ch === "f" && body.startsWith("for", i) && wordBefore(i) && boundaryAt(i + 3))
|
|
444
|
+
kwLen = 3;
|
|
445
|
+
else if (ch === "w" && body.startsWith("while", i) && wordBefore(i) && boundaryAt(i + 5))
|
|
446
|
+
kwLen = 5;
|
|
447
|
+
else if (ch === "d" && body.startsWith("do", i) && wordBefore(i) && boundaryAt(i + 2))
|
|
448
|
+
kwLen = 2;
|
|
449
|
+
if (kwLen > 0) {
|
|
450
|
+
let j = skipWs(i + kwLen);
|
|
451
|
+
// for/while carry a `(condition)` before the body; `do` does not. Skip the
|
|
452
|
+
// condition so its inner `;`s (e.g. `for (i=0; i<n; i++)`) aren't read as a
|
|
453
|
+
// braceless body terminator.
|
|
454
|
+
if ((kwLen === 3 || kwLen === 5) && body[j] === "(") {
|
|
455
|
+
const close = matchParen(j);
|
|
456
|
+
if (close < 0)
|
|
457
|
+
continue;
|
|
458
|
+
j = skipWs(close + 1);
|
|
459
|
+
}
|
|
460
|
+
if (body[j] === "{") {
|
|
461
|
+
loopBraces.add(j); // braced body — recorded when the stack pops it
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
ranges.push({ start: j - 1, end: stmtEnd(j) }); // braceless single statement
|
|
465
|
+
}
|
|
466
|
+
i = j - 1;
|
|
149
467
|
continue;
|
|
150
468
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
469
|
+
if (ch === "{") {
|
|
470
|
+
stack.push({ openIdx: i, isLoop: loopBraces.has(i) });
|
|
471
|
+
}
|
|
472
|
+
else if (ch === "}") {
|
|
473
|
+
const top = stack.pop();
|
|
474
|
+
if (top?.isLoop)
|
|
475
|
+
ranges.push({ start: top.openIdx, end: i });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return ranges;
|
|
479
|
+
}
|
|
480
|
+
// Inline SOQL queries are found by soql-fields findInlineQueries: from
|
|
481
|
+
// `[SELECT` to the `]` that closes it at bracket depth 0, outside string
|
|
482
|
+
// literals (a bind such as `:ids[0]` does not end the query). The object is
|
|
483
|
+
// taken by parseSoql, which finds the FROM at paren-depth 0 so a child subquery
|
|
484
|
+
// (`(SELECT … FROM Contacts)`) or a semi-join (`WHERE Id IN (SELECT … FROM
|
|
485
|
+
// Contact)`) doesn't get mistaken for the outer object.
|
|
486
|
+
export const SOSL_RE = /\[\s*FIND\b/gi;
|
|
487
|
+
/** Dynamic query entry points (the query text is a runtime string). */
|
|
488
|
+
export const DYNAMIC_QUERY_RE = /\b(Database|Search)\s*\.\s*(query|queryWithBinds|countQuery|countQueryWithBinds|getQueryLocator|getQueryLocatorWithBinds)\s*\(/gi;
|
|
489
|
+
/**
|
|
490
|
+
* T15 I-5: one EXECUTES_SOSL `SOSL:*` edge when `code` has an inline SOSL
|
|
491
|
+
* query, stamped `inLoop` when any of them sits inside a loop body.
|
|
492
|
+
*/
|
|
493
|
+
export function emitSoslEdge(ctx, srcQname, code, inLoopAt, edges) {
|
|
494
|
+
let found = false;
|
|
495
|
+
let inLoop = false;
|
|
496
|
+
for (const m of code.matchAll(new RegExp(SOSL_RE.source, "gi"))) {
|
|
497
|
+
found = true;
|
|
498
|
+
if (inLoopAt(m.index ?? 0))
|
|
499
|
+
inLoop = true;
|
|
500
|
+
}
|
|
501
|
+
if (found) {
|
|
502
|
+
edges.push(makeEdge(ctx, srcQname, REL_TYPES.EXECUTES_SOSL, "SOSL:*", inLoop ? { inLoop: true } : {}));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* T15 I-5: the dynamic query calls (`Database.query`, `Search.query`, ...)
|
|
507
|
+
* inside a loop body of `code` (strings/comments stripped), as sorted
|
|
508
|
+
* distinct canonical names. Stamped on the method / trigger node as
|
|
509
|
+
* `dynamicQueryInLoop`; empty when none.
|
|
510
|
+
*/
|
|
511
|
+
export function dynamicQueriesInLoop(code, inLoopAt) {
|
|
512
|
+
const out = new Set();
|
|
513
|
+
for (const m of code.matchAll(new RegExp(DYNAMIC_QUERY_RE.source, "gi"))) {
|
|
514
|
+
if (!inLoopAt(m.index ?? 0))
|
|
155
515
|
continue;
|
|
516
|
+
const cls = (m[1] ?? "").toLowerCase() === "search" ? "Search" : "Database";
|
|
517
|
+
const op = (m[2] ?? "").toLowerCase();
|
|
518
|
+
const canon = [
|
|
519
|
+
"query",
|
|
520
|
+
"queryWithBinds",
|
|
521
|
+
"countQuery",
|
|
522
|
+
"countQueryWithBinds",
|
|
523
|
+
"getQueryLocator",
|
|
524
|
+
"getQueryLocatorWithBinds",
|
|
525
|
+
].find((k) => k.toLowerCase() === op) ?? op;
|
|
526
|
+
out.add(`${cls}.${canon}`);
|
|
527
|
+
}
|
|
528
|
+
return [...out].sort();
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Extract the outer object + SELECT clause from a SOQL query's inner text (the
|
|
532
|
+
* part after `[SELECT`). The object is the identifier after the first `FROM`
|
|
533
|
+
* encountered at parenthesis depth 0; subquery/semi-join FROMs (depth > 0) are
|
|
534
|
+
* skipped. Returns object=null for a malformed query.
|
|
535
|
+
*/
|
|
536
|
+
export function parseSoql(inner) {
|
|
537
|
+
let depth = 0;
|
|
538
|
+
for (let i = 0; i < inner.length; i++) {
|
|
539
|
+
const c = inner[i];
|
|
540
|
+
if (c === "(")
|
|
541
|
+
depth += 1;
|
|
542
|
+
else if (c === ")") {
|
|
543
|
+
if (depth > 0)
|
|
544
|
+
depth -= 1;
|
|
156
545
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
i
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
546
|
+
else if (depth === 0 &&
|
|
547
|
+
(c === "f" || c === "F") &&
|
|
548
|
+
/^from\b/i.test(inner.slice(i)) &&
|
|
549
|
+
/\W/.test(inner[i - 1] ?? " ")) {
|
|
550
|
+
const m = inner.slice(i + 4).match(/^\s+([A-Za-z_][\w.]*)/);
|
|
551
|
+
if (m?.[1])
|
|
552
|
+
return { object: m[1], selectClause: inner.slice(0, i) };
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return { object: null, selectClause: inner };
|
|
556
|
+
}
|
|
557
|
+
export const DML_RE = /\b(insert|update|delete|upsert|undelete|merge)\s+([A-Za-z_][\w.]*|\[)/gi;
|
|
558
|
+
/** `Database.insert(recs)`, `Database.update(recs, false)`, `Database.INSERT(…)`:
|
|
559
|
+
* the Database class DML methods (Apex is case-insensitive). Group 2 is the
|
|
560
|
+
* first argument when it is a plain name. */
|
|
561
|
+
export const DATABASE_DML_RE = /\bDatabase\s*\.\s*(insert|update|upsert|delete|undelete|merge)\s*\(\s*([A-Za-z_][\w.]*)?/gi;
|
|
562
|
+
/**
|
|
563
|
+
* EXECUTES_DML `DML:<op>` for DML statements (`insert x;`, `delete [SELECT …]`)
|
|
564
|
+
* and Database DML calls (`Database.insert(x)`) in `code` (strings/comments
|
|
565
|
+
* stripped), stamped `inLoop` when inside a loop body. `target` is the record
|
|
566
|
+
* expression's name when there is one.
|
|
567
|
+
*
|
|
568
|
+
* When the record expression's sObject type is known (see `recordTypeAt`:
|
|
569
|
+
* `new T(…)`, an inline query, a variable `objects.lookup` types, or
|
|
570
|
+
* `Trigger.new` / `Trigger.old`), also EXECUTES_DML `CustomObject:<T>`, like
|
|
571
|
+
* EXECUTES_SOQL, one per object with the sorted operations in `ops`. That edge
|
|
572
|
+
* carries no `inLoop`: the governor checks read the `DML:<op>` marker, so a
|
|
573
|
+
* statement in a loop is reported once.
|
|
574
|
+
*/
|
|
575
|
+
export function emitDmlEdges(ctx, srcQname, code, inLoopAt, edges, objects = {}) {
|
|
576
|
+
const found = [];
|
|
577
|
+
for (const m of code.matchAll(new RegExp(DML_RE.source, "gi"))) {
|
|
578
|
+
const raw = m[2] ?? "";
|
|
579
|
+
const i = m.index ?? 0;
|
|
580
|
+
const target = raw === "[" || raw.toLowerCase() === "new" ? undefined : raw;
|
|
581
|
+
found.push({ i, op: (m[1] ?? "").toLowerCase(), target, arg: i + m[0].length - raw.length });
|
|
582
|
+
}
|
|
583
|
+
for (const m of code.matchAll(new RegExp(DATABASE_DML_RE.source, "gi"))) {
|
|
584
|
+
const t = m[2] && m[2].toLowerCase() !== "new" ? m[2] : undefined;
|
|
585
|
+
const i = m.index ?? 0;
|
|
586
|
+
const arg = i + m[0].length - (m[2] ?? "").length;
|
|
587
|
+
found.push({ i, op: (m[1] ?? "").toLowerCase(), target: t, arg });
|
|
588
|
+
}
|
|
589
|
+
found.sort((a, b) => a.i - b.i);
|
|
590
|
+
const byObject = new Map();
|
|
591
|
+
for (const f of found) {
|
|
592
|
+
edges.push(makeEdge(ctx, srcQname, REL_TYPES.EXECUTES_DML, `DML:${f.op}`, {
|
|
593
|
+
...(f.target !== undefined ? { target: f.target } : {}),
|
|
594
|
+
...(inLoopAt(f.i) ? { inLoop: true } : {}),
|
|
595
|
+
}));
|
|
596
|
+
const object = recordTypeAt(code, f.arg, objects);
|
|
597
|
+
if (!object)
|
|
598
|
+
continue;
|
|
599
|
+
const obj = stripNs(object, ctx.namespace);
|
|
600
|
+
const hit = byObject.get(obj.toLowerCase()) ?? { object: obj, ops: new Set() };
|
|
601
|
+
hit.ops.add(f.op);
|
|
602
|
+
byObject.set(obj.toLowerCase(), hit);
|
|
603
|
+
}
|
|
604
|
+
for (const { object, ops } of byObject.values()) {
|
|
605
|
+
edges.push(makeEdge(ctx, srcQname, REL_TYPES.EXECUTES_DML, `CustomObject:${object}`, {
|
|
606
|
+
ops: [...ops].sort(),
|
|
607
|
+
}));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Inline `[SELECT …]` queries in `code`: EXECUTES_SOQL on the outer FROM
|
|
612
|
+
* object (with `query`, `inLoop`) and READS_FIELD for every plain field the
|
|
613
|
+
* outer query names (SELECT list, WHERE / HAVING operands, GROUP BY, ORDER
|
|
614
|
+
* BY). Dotted relationship fields are not this object's fields: they are
|
|
615
|
+
* appended to `paths`.
|
|
616
|
+
*/
|
|
617
|
+
export function emitInlineSoqlEdges(ctx, srcQname, code, inLoopAt, edges, paths, children) {
|
|
618
|
+
for (const sq of findInlineQueries(code)) {
|
|
619
|
+
// Bind expressions (`:ids[0]`, an inline query held in a bind) are Apex,
|
|
620
|
+
// not this query's SOQL: blank their bracket groups for the grammar pass.
|
|
621
|
+
const inner = blankBracketGroups(sq.inner);
|
|
622
|
+
const { object } = parseSoql(inner);
|
|
623
|
+
const obj = stripNs(object ?? "", ctx.namespace);
|
|
624
|
+
edges.push(makeEdge(ctx, srcQname, REL_TYPES.EXECUTES_SOQL, `CustomObject:${obj}`, {
|
|
625
|
+
query: sq.text.trim(),
|
|
626
|
+
...(inLoopAt(sq.index) ? { inLoop: true } : {}),
|
|
627
|
+
}));
|
|
628
|
+
if (!obj)
|
|
173
629
|
continue;
|
|
630
|
+
const refs = soqlFieldRefs(inner);
|
|
631
|
+
for (const f of refs.fields)
|
|
632
|
+
edges.push(makeEdge(ctx, srcQname, REL_TYPES.READS_FIELD, `CustomField:${obj}.${f}`));
|
|
633
|
+
for (const p of refs.paths)
|
|
634
|
+
paths.push({ object: obj, path: p, rel: REL_TYPES.READS_FIELD });
|
|
635
|
+
// `TYPEOF What WHEN Account THEN Name …`: branch fields are read on the branch's type.
|
|
636
|
+
for (const b of refs.typeOf) {
|
|
637
|
+
const bObj = stripNs(b.object, ctx.namespace);
|
|
638
|
+
for (const f of b.fields)
|
|
639
|
+
edges.push(makeEdge(ctx, srcQname, REL_TYPES.READS_FIELD, `CustomField:${bObj}.${f}`));
|
|
640
|
+
for (const p of b.paths)
|
|
641
|
+
paths.push({ object: bObj, path: p, rel: REL_TYPES.READS_FIELD });
|
|
174
642
|
}
|
|
175
|
-
if (
|
|
643
|
+
if (children)
|
|
644
|
+
addChildRelationships(children, obj, parseSoql(inner).selectClause);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/** Literal custom-field names: `'Name__c'` (optional namespace), nothing else in the quotes. */
|
|
648
|
+
const FIELD_LITERAL_RE = /'((?:[A-Za-z][A-Za-z0-9]*__)?[A-Za-z][A-Za-z0-9_]*__c)'/g;
|
|
649
|
+
/** Declared types that are never an sObject. */
|
|
650
|
+
const NOT_SOBJECT = new Set(["sobject", "string", "object", "id", "map", "list", "set"]);
|
|
651
|
+
/** Records `apexFieldNameLiterals` on a method node (see ApexFieldNameLiteral). */
|
|
652
|
+
function setFieldNameLiterals(node, rawDecl, decls) {
|
|
653
|
+
const objects = new Set();
|
|
654
|
+
for (const d of decls) {
|
|
655
|
+
for (const t of [d.type, d.elem]) {
|
|
656
|
+
if (!t || t.includes(".") || NOT_SOBJECT.has(t.toLowerCase()))
|
|
657
|
+
continue;
|
|
658
|
+
if (/^[A-Za-z]\w*$/.test(t))
|
|
659
|
+
objects.add(t);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (objects.size === 0)
|
|
663
|
+
return;
|
|
664
|
+
const fields = new Set();
|
|
665
|
+
for (const m of stripCommentsOnly(rawDecl).matchAll(FIELD_LITERAL_RE)) {
|
|
666
|
+
if (m[1])
|
|
667
|
+
fields.add(m[1]);
|
|
668
|
+
}
|
|
669
|
+
if (fields.size === 0)
|
|
670
|
+
return;
|
|
671
|
+
const objs = [...objects].sort().slice(0, 20);
|
|
672
|
+
node.attributes.apexFieldNameLiterals = [...fields]
|
|
673
|
+
.sort()
|
|
674
|
+
.slice(0, 200)
|
|
675
|
+
.map((field) => ({ field, objects: objs }));
|
|
676
|
+
}
|
|
677
|
+
/** Append the depth-1 `(SELECT …)` groups of `selectClause`, deduped on
|
|
678
|
+
* (object, relationship) with their fields merged. One linear pass. */
|
|
679
|
+
function addChildRelationships(into, object, selectClause) {
|
|
680
|
+
let depth = 0;
|
|
681
|
+
let start = -1;
|
|
682
|
+
for (let i = 0; i < selectClause.length; i++) {
|
|
683
|
+
const c = selectClause[i];
|
|
684
|
+
if (c === "(") {
|
|
176
685
|
depth += 1;
|
|
177
|
-
|
|
686
|
+
if (depth === 1)
|
|
687
|
+
start = i + 1;
|
|
688
|
+
}
|
|
689
|
+
else if (c === ")" && depth > 0) {
|
|
178
690
|
depth -= 1;
|
|
179
|
-
if (depth
|
|
180
|
-
|
|
691
|
+
if (depth !== 0)
|
|
692
|
+
continue;
|
|
693
|
+
const sub = /^\s*select\b/i.exec(selectClause.slice(start, i));
|
|
694
|
+
if (!sub)
|
|
695
|
+
continue;
|
|
696
|
+
const refs = soqlFieldRefs(selectClause.slice(start + sub[0].length, i));
|
|
697
|
+
if (!refs.object || refs.object.includes("."))
|
|
698
|
+
continue;
|
|
699
|
+
const hit = into.find((r) => r.object.toLowerCase() === object.toLowerCase() &&
|
|
700
|
+
r.relationship.toLowerCase() === refs.object?.toLowerCase());
|
|
701
|
+
const rec = hit ?? { object, relationship: refs.object, fields: [] };
|
|
702
|
+
if (!hit)
|
|
703
|
+
into.push(rec);
|
|
704
|
+
for (const f of refs.fields)
|
|
705
|
+
if (!rec.fields.some((x) => x.toLowerCase() === f.toLowerCase()))
|
|
706
|
+
rec.fields.push(f);
|
|
181
707
|
}
|
|
182
|
-
|
|
183
|
-
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/** Store child-relationship subqueries on a node (only when there are any). */
|
|
711
|
+
function setChildRelationships(node, rels) {
|
|
712
|
+
if (!node || rels.length === 0)
|
|
713
|
+
return;
|
|
714
|
+
node.attributes.apexChildRelationships = rels;
|
|
715
|
+
}
|
|
716
|
+
/** Append `add` to `into`, deduped on (object, path, rel) case-insensitively. */
|
|
717
|
+
function mergePaths(into, add) {
|
|
718
|
+
const seen = new Set(into.map((p) => `${p.object}|${p.path}|${p.rel}`.toLowerCase()));
|
|
719
|
+
for (const p of add) {
|
|
720
|
+
const k = `${p.object}|${p.path}|${p.rel}`.toLowerCase();
|
|
721
|
+
if (seen.has(k))
|
|
722
|
+
continue;
|
|
723
|
+
seen.add(k);
|
|
724
|
+
into.push(p);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const FIELD_ACCESS_RE = /\b([A-Z][A-Za-z0-9_]*(?:__r)?)\.([A-Za-z_][\w]*?)(?:__c)?\b/g;
|
|
728
|
+
const NAMED_CRED_RE = /['"]callout:([A-Za-z_][\w]*)/g;
|
|
729
|
+
/**
|
|
730
|
+
* Push READS_FIELD / WRITES_FIELD edges for member-access field refs, skipping
|
|
731
|
+
* any (rel, dst) already emitted for this source since `fromIdx` (e.g. a
|
|
732
|
+
* READS_FIELD from a SOQL select list) so existing edge attributes are kept.
|
|
733
|
+
*/
|
|
734
|
+
export function emitFieldAccessEdges(ctx, srcQname, accesses, edges, fromIdx) {
|
|
735
|
+
const seen = new Set();
|
|
736
|
+
for (let i = fromIdx; i < edges.length; i++) {
|
|
737
|
+
const e = edges[i];
|
|
738
|
+
if (e && String(e.srcQualifiedName) === srcQname)
|
|
739
|
+
seen.add(`${e.relType}|${String(e.dstQualifiedName).toLowerCase()}`);
|
|
740
|
+
}
|
|
741
|
+
const push = (rel, dst) => {
|
|
742
|
+
const key = `${rel}|${dst.toLowerCase()}`;
|
|
743
|
+
if (seen.has(key))
|
|
744
|
+
return;
|
|
745
|
+
seen.add(key);
|
|
746
|
+
edges.push(makeEdge(ctx, srcQname, rel, dst, { via: "member-access" }));
|
|
747
|
+
};
|
|
748
|
+
for (const a of accesses) {
|
|
749
|
+
const dst = `CustomField:${stripNs(a.object, ctx.namespace)}.${a.field}`;
|
|
750
|
+
if (a.read)
|
|
751
|
+
push(REL_TYPES.READS_FIELD, dst);
|
|
752
|
+
if (a.write)
|
|
753
|
+
push(REL_TYPES.WRITES_FIELD, dst);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* READS_FIELD `dynamic: true` for the outer SELECT fields and the WHERE /
|
|
758
|
+
* HAVING / GROUP BY / ORDER BY field operands of each folded dynamic SOQL
|
|
759
|
+
* string in `queries` (see foldDynamicSoql, dynamicSoqlClauseFields). Only
|
|
760
|
+
* when the FROM object is literal text; computed fields are skipped. A
|
|
761
|
+
* (src, dst) READS_FIELD already emitted since `fromIdx` is not repeated.
|
|
762
|
+
* Returns the number of clause field operands skipped because FROM is
|
|
763
|
+
* computed (see setDynamicSoqlSkips).
|
|
764
|
+
*/
|
|
765
|
+
export function emitDynamicSoqlEdges(ctx, srcQname, queries, edges, fromIdx) {
|
|
766
|
+
const seen = new Set();
|
|
767
|
+
// Objects this source already queries (static SOQL): an added dynamic edge
|
|
768
|
+
// would replace that edge (the store merges last-wins) and could drop its
|
|
769
|
+
// `inLoop`, so those keep the static edge alone.
|
|
770
|
+
const queried = new Set();
|
|
771
|
+
for (let i = fromIdx; i < edges.length; i++) {
|
|
772
|
+
const e = edges[i];
|
|
773
|
+
if (!e || String(e.srcQualifiedName) !== srcQname)
|
|
774
|
+
continue;
|
|
775
|
+
if (e.relType === REL_TYPES.READS_FIELD)
|
|
776
|
+
seen.add(String(e.dstQualifiedName).toLowerCase());
|
|
777
|
+
else if (e.relType === REL_TYPES.EXECUTES_SOQL)
|
|
778
|
+
queried.add(String(e.dstQualifiedName).toLowerCase());
|
|
779
|
+
}
|
|
780
|
+
let skipped = 0;
|
|
781
|
+
for (const q of queries) {
|
|
782
|
+
const parsed = parseDynamicSoql(q);
|
|
783
|
+
const clause = dynamicSoqlClauseFields(q, parsed);
|
|
784
|
+
skipped += clause.skipped;
|
|
785
|
+
if (!parsed)
|
|
786
|
+
continue;
|
|
787
|
+
const obj = stripNs(parsed.object, ctx.namespace);
|
|
788
|
+
// The object the query reads, like a static `[SELECT …]`. No `inLoop`:
|
|
789
|
+
// loop detection for dynamic queries is `dynamicQueryInLoop` on the node.
|
|
790
|
+
const objDst = `CustomObject:${obj}`;
|
|
791
|
+
if (!queried.has(objDst.toLowerCase())) {
|
|
792
|
+
queried.add(objDst.toLowerCase());
|
|
793
|
+
edges.push(makeEdge(ctx, srcQname, REL_TYPES.EXECUTES_SOQL, objDst, {
|
|
794
|
+
query: q.split(PLACEHOLDER).join("…").trim().slice(0, 500),
|
|
795
|
+
dynamic: true,
|
|
796
|
+
}));
|
|
184
797
|
}
|
|
185
|
-
|
|
186
|
-
|
|
798
|
+
for (const fname of [...parsed.fields, ...clause.fields]) {
|
|
799
|
+
const dst = `CustomField:${obj}.${fname}`;
|
|
800
|
+
if (seen.has(dst.toLowerCase()))
|
|
801
|
+
continue;
|
|
802
|
+
seen.add(dst.toLowerCase());
|
|
803
|
+
edges.push(makeEdge(ctx, srcQname, REL_TYPES.READS_FIELD, dst, { dynamic: true }));
|
|
187
804
|
}
|
|
188
|
-
i += 1;
|
|
189
805
|
}
|
|
190
|
-
|
|
191
|
-
return null; // unbalanced — let caller emit `(?)`
|
|
192
|
-
// No commas + no non-whitespace content between parens = arity 0.
|
|
193
|
-
// Any non-whitespace + N commas = N+1 args.
|
|
194
|
-
return sawNonWhitespace ? argCount + 1 : 0;
|
|
806
|
+
return skipped;
|
|
195
807
|
}
|
|
196
|
-
|
|
808
|
+
/**
|
|
809
|
+
* Parse stat on a source node: `dynamicSoqlFieldsSkipped`, the dynamic SOQL
|
|
810
|
+
* clause field operands not attributed because the query's FROM is computed
|
|
811
|
+
* (only when there are any). The apex ref resolver sums it.
|
|
812
|
+
*/
|
|
813
|
+
export function setDynamicSoqlSkips(node, skipped) {
|
|
814
|
+
if (!node || skipped <= 0)
|
|
815
|
+
return;
|
|
816
|
+
const attrs = node.attributes;
|
|
817
|
+
attrs.dynamicSoqlFieldsSkipped = (attrs.dynamicSoqlFieldsSkipped ?? 0) + skipped;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Collapse repeated EXECUTES_SOQL / EXECUTES_DML facts with the same
|
|
821
|
+
* (src, rel, dst) into one, in place of first occurrence. The store merges
|
|
822
|
+
* edges last-wins on that key, so without this an out-of-loop query after an
|
|
823
|
+
* in-loop one on the same object erased `inLoop`. The kept fact carries
|
|
824
|
+
* `inLoop: true` if any occurrence did (taking that occurrence's attributes),
|
|
825
|
+
* and `count` = number of occurrences when more than one.
|
|
826
|
+
*/
|
|
827
|
+
export function aggregateGovernorFacts(edges) {
|
|
828
|
+
const out = [];
|
|
829
|
+
const byKey = new Map();
|
|
830
|
+
for (const e of edges) {
|
|
831
|
+
if (e.relType !== REL_TYPES.EXECUTES_SOQL && e.relType !== REL_TYPES.EXECUTES_DML) {
|
|
832
|
+
out.push(e);
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
const key = `${e.relType}|${String(e.srcQualifiedName)}|${String(e.dstQualifiedName).toLowerCase()}`;
|
|
836
|
+
const prev = byKey.get(key);
|
|
837
|
+
if (!prev) {
|
|
838
|
+
const copy = { ...e, attributes: { ...e.attributes } };
|
|
839
|
+
byKey.set(key, { edge: copy, count: 1 });
|
|
840
|
+
out.push(copy);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
prev.count += 1;
|
|
844
|
+
const attrs = e.attributes;
|
|
845
|
+
if (attrs.inLoop === true && prev.edge.attributes.inLoop !== true)
|
|
846
|
+
prev.edge.attributes = { ...attrs };
|
|
847
|
+
prev.edge.attributes.count = prev.count;
|
|
848
|
+
}
|
|
849
|
+
return out;
|
|
850
|
+
}
|
|
851
|
+
const LOWER_NEW_RE = /\bnew\s+([a-z][A-Za-z0-9_]*)\s*\(/g;
|
|
852
|
+
const LOWER_STATIC_CALL_RE = /(?<![\w.])([a-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*)\s*\(/g;
|
|
853
|
+
const NOT_A_CLASS = new Set(["this", "super"]);
|
|
197
854
|
function parseClassNameAndIsTest(body) {
|
|
198
855
|
const testTargets = new Set();
|
|
856
|
+
const lowerTargets = [];
|
|
199
857
|
const m = body.match(/@isTest\s*\(\s*seeAllData\s*=\s*[^)]*\)|@isTest/i);
|
|
200
858
|
if (!m)
|
|
201
|
-
return { isTest: false, testTargets };
|
|
859
|
+
return { isTest: false, testTargets, lowerTargets };
|
|
860
|
+
for (const re of [LOWER_NEW_RE, LOWER_STATIC_CALL_RE]) {
|
|
861
|
+
for (const x of body.matchAll(new RegExp(re.source, "g"))) {
|
|
862
|
+
const t = x[1] ?? "";
|
|
863
|
+
if (t && !NOT_A_CLASS.has(t))
|
|
864
|
+
lowerTargets.push(t);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
202
867
|
// crude: find class names referenced via `new X(` or `X.foo(`
|
|
203
868
|
const re1 = new RegExp(NEW_INSTANCE_RE.source, "g");
|
|
204
869
|
let mm = re1.exec(body);
|
|
@@ -214,7 +879,7 @@ function parseClassNameAndIsTest(body) {
|
|
|
214
879
|
testTargets.add(mm[1]);
|
|
215
880
|
mm = re2.exec(body);
|
|
216
881
|
}
|
|
217
|
-
return { isTest: true, testTargets };
|
|
882
|
+
return { isTest: true, testTargets, lowerTargets };
|
|
218
883
|
}
|
|
219
884
|
export class ApexClassParser {
|
|
220
885
|
category = METADATA_CATEGORY.APEX_CLASS;
|
|
@@ -224,7 +889,29 @@ export class ApexClassParser {
|
|
|
224
889
|
const edges = [];
|
|
225
890
|
const snippets = [];
|
|
226
891
|
const className = stripNs(input.className, ctx.namespace);
|
|
227
|
-
const
|
|
892
|
+
const shadowsManaged = extractShadowsManaged(input.metaXml);
|
|
893
|
+
const shadowAttr = shadowsManaged.length > 0 ? { shadowsManaged } : {};
|
|
894
|
+
// Fold the collision into the hash (only when present, so every other
|
|
895
|
+
// node keeps its hash): node merge skips attributes when the hash is
|
|
896
|
+
// unchanged, and the stored local node must pick up the attr.
|
|
897
|
+
const fileHash = sha256(`${APEX_NODE_HASH_SALT}\n${input.body}${shadowsManaged.length > 0 ? `\nshadowsManaged:${shadowsManaged.join(",")}` : ""}`);
|
|
898
|
+
// Managed-package Apex (and any member whose Body the extractor couldn't
|
|
899
|
+
// fetch) arrives with an empty body. That is NOT a parse failure — the
|
|
900
|
+
// class exists, we just can't see its source. Emit a bare node so
|
|
901
|
+
// referencing edges resolve to a real target, instead of a misleading
|
|
902
|
+
// ParseError (the apex-parser throws a non-Error on empty input, which the
|
|
903
|
+
// catch below would record as the useless message "[object Object]").
|
|
904
|
+
if (input.body.trim() === "") {
|
|
905
|
+
nodes.push(makeNode(ctx, "ApexClass", `ApexClass:${className}`, {
|
|
906
|
+
name: className,
|
|
907
|
+
modifiers: [],
|
|
908
|
+
isTest: false,
|
|
909
|
+
apiVersion: extractApiVersion(input.metaXml),
|
|
910
|
+
bodyUnavailable: true,
|
|
911
|
+
...shadowAttr,
|
|
912
|
+
}, fileHash));
|
|
913
|
+
return { nodes, edges, snippets };
|
|
914
|
+
}
|
|
228
915
|
// Read parser mode once per parse — env var lets us flip between
|
|
229
916
|
// regex (the legacy path), ast (the new precise extractor), and both
|
|
230
917
|
// (regex authoritative; ast diff logged for shadow validation).
|
|
@@ -238,25 +925,48 @@ export class ApexClassParser {
|
|
|
238
925
|
// When parserMode != "regex", retain the parsed compilationUnit tree
|
|
239
926
|
// so the AST extractor can walk it without parsing twice.
|
|
240
927
|
let parsedTree = null;
|
|
928
|
+
let lexer = null;
|
|
929
|
+
let parser = null;
|
|
241
930
|
try {
|
|
242
931
|
// Lazy-load to avoid pulling apex-parser into other parsers' worker.
|
|
243
932
|
const apex = await import("apex-parser");
|
|
244
933
|
const { ApexLexer, ApexParser, CommonTokenStream, CaseInsensitiveInputStream, ThrowingErrorListener, } = apex;
|
|
245
|
-
const input1 = new CaseInsensitiveInputStream("file", input.body);
|
|
246
|
-
|
|
934
|
+
const input1 = new CaseInsensitiveInputStream("file", rewriteNullCoalescing(input.body));
|
|
935
|
+
lexer = new ApexLexer(input1);
|
|
247
936
|
lexer.removeErrorListeners();
|
|
248
937
|
lexer.addErrorListener(new ThrowingErrorListener());
|
|
249
938
|
const tokens = new CommonTokenStream(lexer);
|
|
250
|
-
|
|
939
|
+
parser = new ApexParser(tokens);
|
|
251
940
|
parser.removeErrorListeners();
|
|
252
941
|
parser.addErrorListener(new ThrowingErrorListener());
|
|
253
942
|
parsedTree = parser.compilationUnit();
|
|
254
943
|
}
|
|
255
944
|
catch (err) {
|
|
256
|
-
const errMsg =
|
|
945
|
+
const errMsg = formatParseError(err);
|
|
257
946
|
nodes.push(makeNode(ctx, "ParseError", `ParseError:ApexClass:${className}`, { className, message: errMsg }, sha256(`${className}:${errMsg}`)));
|
|
947
|
+
// Keep a stub for the type itself so grants, references and calls to it
|
|
948
|
+
// still resolve; `parseFailed` marks it as having no extracted body.
|
|
949
|
+
const stubSrc = stripCommentsAndStrings(input.body);
|
|
950
|
+
const stubHeader = extractClassHeader(stubSrc);
|
|
951
|
+
const stubLabel = stubHeader.isInterface ? "ApexInterface" : "ApexClass";
|
|
952
|
+
// Cheap fallback when the broken body defeats the header scan: an
|
|
953
|
+
// `@isTest` annotation before the first class keyword.
|
|
954
|
+
const stubIsTest = stubHeader.isTest || /@istest\b[^{;]*?\bclass\b/i.test(stubSrc);
|
|
955
|
+
nodes.push(makeNode(ctx, stubLabel, `${stubLabel}:${className}`, {
|
|
956
|
+
name: className,
|
|
957
|
+
modifiers: [],
|
|
958
|
+
isTest: stubIsTest,
|
|
959
|
+
apiVersion: extractApiVersion(input.metaXml),
|
|
960
|
+
parseFailed: true,
|
|
961
|
+
...shadowAttr,
|
|
962
|
+
}, fileHash));
|
|
258
963
|
return { nodes, edges };
|
|
259
964
|
}
|
|
965
|
+
finally {
|
|
966
|
+
// The tree (if kept) does not depend on the DFA cache.
|
|
967
|
+
if (parser)
|
|
968
|
+
noteApexSyntaxCheck(lexer, parser);
|
|
969
|
+
}
|
|
260
970
|
const cleaned = stripCommentsAndStrings(input.body);
|
|
261
971
|
const header = extractClassHeader(cleaned);
|
|
262
972
|
const effectiveName = header.name || className;
|
|
@@ -264,21 +974,78 @@ export class ApexClassParser {
|
|
|
264
974
|
? `ApexInterface:${effectiveName}`
|
|
265
975
|
: `ApexClass:${effectiveName}`;
|
|
266
976
|
const apiVersion = extractApiVersion(input.metaXml);
|
|
977
|
+
const dispatch = extractVlocityDispatch(input.body, header.implementsList);
|
|
978
|
+
// `final` String fields: an IP / DataRaptor name passed as a constant.
|
|
979
|
+
const stringConstants = classStringConstants(input.body);
|
|
267
980
|
nodes.push(makeNode(ctx, header.isInterface ? "ApexInterface" : "ApexClass", classQname, {
|
|
268
981
|
name: effectiveName,
|
|
269
982
|
modifiers: header.modifiers,
|
|
270
983
|
isTest: header.isTest,
|
|
271
984
|
apiVersion,
|
|
985
|
+
...(dispatch ? { vlocityDispatch: dispatch } : {}),
|
|
986
|
+
...shadowAttr,
|
|
272
987
|
}, fileHash));
|
|
273
|
-
if (header.
|
|
988
|
+
if (header.isInterface) {
|
|
989
|
+
// An interface extends interfaces, possibly several.
|
|
990
|
+
for (const parent of header.extendsList) {
|
|
991
|
+
edges.push(makeEdge(ctx, classQname, REL_TYPES.EXTENDS, `ApexInterface:${stripNs(parent, ctx.namespace)}`));
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
else if (header.extendsClass) {
|
|
274
995
|
edges.push(makeEdge(ctx, classQname, REL_TYPES.EXTENDS, `ApexClass:${stripNs(header.extendsClass, ctx.namespace)}`));
|
|
275
996
|
}
|
|
276
997
|
for (const impl of header.implementsList) {
|
|
277
998
|
edges.push(makeEdge(ctx, classQname, REL_TYPES.IMPLEMENTS, `ApexInterface:${stripNs(impl, ctx.namespace)}`));
|
|
278
999
|
}
|
|
279
1000
|
// Methods
|
|
280
|
-
const
|
|
1001
|
+
const usedDecls = new Set();
|
|
1002
|
+
const maskedBody = maskApex(input.body);
|
|
1003
|
+
const methods = findMethods(cleaned, effectiveName);
|
|
1004
|
+
// Member-access field refs: each method falls back to the fields/properties
|
|
1005
|
+
// declared directly in ITS enclosing class body (constructor/initializer
|
|
1006
|
+
// locals and other inner classes' fields are excluded); types declared in
|
|
1007
|
+
// this file (inner classes, enums, interfaces) are never SObjects.
|
|
1008
|
+
const scopes = classScopes(cleaned);
|
|
1009
|
+
const localTypes = new Set([...cleaned.matchAll(/\b(?:class|enum|interface)\s+([A-Za-z_]\w*)/gi)].map((x) => (x[1] ?? "").toLowerCase()));
|
|
1010
|
+
// Fields of the method's class and of every enclosing class: an inner
|
|
1011
|
+
// class can use its outer class's (static) fields.
|
|
1012
|
+
const enclosingFields = (pos) => {
|
|
1013
|
+
const out = [];
|
|
1014
|
+
const seen = new Set();
|
|
1015
|
+
for (let c = scopes.classAt(pos); c >= 0 && !seen.has(c); c = scopes.classAt(c)) {
|
|
1016
|
+
seen.add(c);
|
|
1017
|
+
out.push(...scopes.fields(c));
|
|
1018
|
+
}
|
|
1019
|
+
return out;
|
|
1020
|
+
};
|
|
1021
|
+
const refScope = {
|
|
1022
|
+
selfName: effectiveName,
|
|
1023
|
+
localTypes,
|
|
1024
|
+
// File-wide set: used for code outside method bodies and for test-class
|
|
1025
|
+
// linkage. Each method gets its own set below (its params, its body's
|
|
1026
|
+
// declarations and its enclosing class's fields).
|
|
1027
|
+
localVars: localVarNames([
|
|
1028
|
+
...collectDecls(cleaned),
|
|
1029
|
+
...methods.flatMap((m) => paramDecls(m.params)),
|
|
1030
|
+
]),
|
|
1031
|
+
};
|
|
1032
|
+
// Methods declared in this file (inner classes' too: all are qnamed
|
|
1033
|
+
// under the outer class), for same-class call targets.
|
|
1034
|
+
const declared = new Map();
|
|
281
1035
|
for (const m of methods) {
|
|
1036
|
+
const d = declared.get(m.name.toLowerCase()) ?? { name: m.name, arities: new Set() };
|
|
1037
|
+
d.arities.add(m.params.length);
|
|
1038
|
+
declared.set(m.name.toLowerCase(), d);
|
|
1039
|
+
}
|
|
1040
|
+
const parentName = header.isInterface ? null : header.extendsClass;
|
|
1041
|
+
emitPageRefEdges(ctx, classQname, cleaned, edges);
|
|
1042
|
+
const methodQueries = [];
|
|
1043
|
+
const methodNodes = new Map();
|
|
1044
|
+
const classPaths = [];
|
|
1045
|
+
const classChildren = [];
|
|
1046
|
+
const declRanges = [];
|
|
1047
|
+
for (const m of methods) {
|
|
1048
|
+
const methodEdgeStart = edges.length;
|
|
282
1049
|
const methodQname = `ApexMethod:${effectiveName}.${m.name}(${m.params.length})`;
|
|
283
1050
|
const annotationsLower = m.annotations.map((a) => a.toLowerCase());
|
|
284
1051
|
const auraEnabled = annotationsLower.includes("auraenabled");
|
|
@@ -288,7 +1055,7 @@ export class ApexClassParser {
|
|
|
288
1055
|
const isTestMethod = annotationsLower.includes("istest") || annotationsLower.includes("testsetup");
|
|
289
1056
|
const httpVerbs = ["httpget", "httppost", "httpput", "httppatch", "httpdelete"].filter((v) => annotationsLower.includes(v));
|
|
290
1057
|
const isTestLabel = isTestMethod || (header.isTest && m.modifiers.includes("static"));
|
|
291
|
-
|
|
1058
|
+
const methodNode = makeNode(ctx, isTestLabel ? "TestMethod" : "ApexMethod", methodQname, {
|
|
292
1059
|
name: m.name,
|
|
293
1060
|
arity: m.params.length,
|
|
294
1061
|
returnType: m.returnType,
|
|
@@ -307,7 +1074,9 @@ export class ApexClassParser {
|
|
|
307
1074
|
// or @isTest on the enclosing class with the method static.
|
|
308
1075
|
// Never derived from filename.
|
|
309
1076
|
isTest: isTestLabel,
|
|
310
|
-
}, sha256(m.body))
|
|
1077
|
+
}, sha256(`${APEX_NODE_HASH_SALT}\n${m.body}`));
|
|
1078
|
+
nodes.push(methodNode);
|
|
1079
|
+
methodNodes.set(methodQname, methodNode);
|
|
311
1080
|
edges.push(makeEdge(ctx, classQname, REL_TYPES.CONTAINS_METHOD, methodQname));
|
|
312
1081
|
// Snippet: emit the raw method body from the ORIGINAL source (preserves
|
|
313
1082
|
// comments + strings) keyed on the method's qname.
|
|
@@ -340,49 +1109,67 @@ export class ApexClassParser {
|
|
|
340
1109
|
if (endLine != null)
|
|
341
1110
|
snippetRec.endLine = endLine;
|
|
342
1111
|
snippets.push(snippetRec);
|
|
1112
|
+
// Apex -> Vlocity calls (IP / DataRaptor), extracted from the raw body.
|
|
1113
|
+
// `rawBody` above locates the method by first textual occurrence of its
|
|
1114
|
+
// name (which can be a call site), so locate the declaration properly here.
|
|
1115
|
+
const decl = declRawBody(input.body, maskedBody, m, usedDecls);
|
|
1116
|
+
if (decl)
|
|
1117
|
+
declRanges.push(decl);
|
|
1118
|
+
const rawDecl = decl?.text ?? null;
|
|
1119
|
+
const vc = extractVlocityCalls(rawDecl ?? "", stringConstants, m.params);
|
|
1120
|
+
for (const k of vc.ipKeys)
|
|
1121
|
+
edges.push(makeEdge(ctx, methodQname, REL_TYPES.APEX_CALLS_IP, `IntegrationProcedure:${k}`));
|
|
1122
|
+
for (const d of vc.drNames)
|
|
1123
|
+
edges.push(makeEdge(ctx, methodQname, REL_TYPES.APEX_CALLS_DR, `DataRaptor:${d}`));
|
|
1124
|
+
if (rawDecl !== null) {
|
|
1125
|
+
setFieldNameLiterals(methodNode, rawDecl, [
|
|
1126
|
+
...paramDecls(m.params),
|
|
1127
|
+
...collectDecls(m.body),
|
|
1128
|
+
]);
|
|
1129
|
+
}
|
|
343
1130
|
// Body analysis (regex path — gated when parserMode === "ast").
|
|
344
1131
|
const body = m.body;
|
|
1132
|
+
const methodPaths = [];
|
|
1133
|
+
const accessPaths = [];
|
|
1134
|
+
const fieldAccesses = extractFieldAccesses(body, {
|
|
1135
|
+
decls: [...paramDecls(m.params), ...collectDecls(body)],
|
|
1136
|
+
outer: scopes.fields(scopes.classAt(m.startIdx)),
|
|
1137
|
+
excludedTypes: localTypes,
|
|
1138
|
+
paths: accessPaths,
|
|
1139
|
+
});
|
|
345
1140
|
if (parserMode === "ast") {
|
|
1141
|
+
setRelationshipPaths(methodNode, accessPaths);
|
|
1142
|
+
emitFieldAccessEdges(ctx, methodQname, fieldAccesses, edges, methodEdgeStart);
|
|
346
1143
|
// AST mode handles per-method edges in a single tree walk after this loop;
|
|
347
1144
|
// skip the regex extraction here to avoid duplicate / inconsistent edges.
|
|
348
1145
|
continue;
|
|
349
1146
|
}
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
sq = soqlRe.exec(body);
|
|
370
|
-
}
|
|
371
|
-
// SOSL
|
|
372
|
-
const soslRe = new RegExp(SOSL_RE.source, "gi");
|
|
373
|
-
if (soslRe.test(body)) {
|
|
374
|
-
edges.push(makeEdge(ctx, methodQname, REL_TYPES.EXECUTES_SOSL, "SOSL:*"));
|
|
375
|
-
}
|
|
376
|
-
// DML
|
|
377
|
-
const dmlRe = new RegExp(DML_RE.source, "gi");
|
|
378
|
-
let dm = dmlRe.exec(body);
|
|
379
|
-
while (dm !== null) {
|
|
380
|
-
const op = (dm[1] ?? "").toLowerCase();
|
|
381
|
-
edges.push(makeEdge(ctx, methodQname, REL_TYPES.EXECUTES_DML, `DML:${op}`, {
|
|
382
|
-
target: dm[2],
|
|
383
|
-
}));
|
|
384
|
-
dm = dmlRe.exec(body);
|
|
1147
|
+
// Loop-body ranges for governor in-loop detection (regex path). The AST
|
|
1148
|
+
// extractor stamps inLoop itself; this gives the DEFAULT (regex) path the
|
|
1149
|
+
// same signal so governor_risk_check works without SFGRAPH_APEX_PARSER=ast.
|
|
1150
|
+
const loopRanges = computeLoopRanges(body);
|
|
1151
|
+
const inLoopAt = (idx) => loopRanges.some((r) => idx > r.start && idx < r.end);
|
|
1152
|
+
// SOQL: EXECUTES_SOQL + READS_FIELD for the outer query's fields.
|
|
1153
|
+
const methodChildren = [];
|
|
1154
|
+
emitInlineSoqlEdges(ctx, methodQname, body, inLoopAt, edges, methodPaths, methodChildren);
|
|
1155
|
+
setChildRelationships(methodNode, methodChildren);
|
|
1156
|
+
mergePaths(methodPaths, accessPaths);
|
|
1157
|
+
setRelationshipPaths(methodNode, methodPaths);
|
|
1158
|
+
emitFieldAccessEdges(ctx, methodQname, fieldAccesses, edges, methodEdgeStart);
|
|
1159
|
+
// SOSL (inLoop when any SOSL sits in a loop body) and dynamic queries
|
|
1160
|
+
// in a loop body (governor risks, T15 I-5).
|
|
1161
|
+
emitSoslEdge(ctx, methodQname, body, inLoopAt, edges);
|
|
1162
|
+
const dynInLoop = dynamicQueriesInLoop(body, inLoopAt);
|
|
1163
|
+
if (dynInLoop.length > 0) {
|
|
1164
|
+
methodNode.attributes.dynamicQueryInLoop = dynInLoop;
|
|
385
1165
|
}
|
|
1166
|
+
// DML (statements and Database.<op>(…) calls), linked to the object
|
|
1167
|
+
// when the record expression's type is known.
|
|
1168
|
+
const outerFields = enclosingFields(m.startIdx);
|
|
1169
|
+
emitDmlEdges(ctx, methodQname, body, inLoopAt, edges, {
|
|
1170
|
+
lookup: makeTypeLookup([...paramDecls(m.params), ...collectDecls(body)], outerFields),
|
|
1171
|
+
excludedTypes: localTypes,
|
|
1172
|
+
});
|
|
386
1173
|
// Named credentials (scan the ORIGINAL method body — strings are stripped from `body`).
|
|
387
1174
|
const ncSource = (() => {
|
|
388
1175
|
const idx = input.body.indexOf(m.name);
|
|
@@ -398,30 +1185,89 @@ export class ApexClassParser {
|
|
|
398
1185
|
let nc = ncRe.exec(ncSource);
|
|
399
1186
|
while (nc !== null) {
|
|
400
1187
|
edges.push(makeEdge(ctx, methodQname, REL_TYPES.CALLS_NAMED_CREDENTIAL, `NamedCredential:${stripNs(nc[1] ?? "", ctx.namespace)}`));
|
|
401
|
-
|
|
1188
|
+
// Continue over the SAME source we started on (ncSource). Re-executing a
|
|
1189
|
+
// stateful /g regex against a different string (`body`) reset its
|
|
1190
|
+
// lastIndex semantics and dropped every named credential after the first.
|
|
1191
|
+
nc = ncRe.exec(ncSource);
|
|
402
1192
|
}
|
|
403
|
-
// Calls to other static methods (best-effort)
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
1193
|
+
// Calls to other static methods (best-effort), constructors and
|
|
1194
|
+
// static-member references.
|
|
1195
|
+
// Scanned on the length-preserving mask (quotes kept, contents blanked)
|
|
1196
|
+
// so a string argument still counts toward the call's arity.
|
|
1197
|
+
const callCode = rawDecl === null ? body : maskApex(rawDecl);
|
|
1198
|
+
const methodScope = {
|
|
1199
|
+
...refScope,
|
|
1200
|
+
localVars: localVarNames([
|
|
1201
|
+
...paramDecls(m.params),
|
|
1202
|
+
...collectDecls(body),
|
|
1203
|
+
...enclosingFields(m.startIdx),
|
|
1204
|
+
]),
|
|
1205
|
+
};
|
|
1206
|
+
emitStaticCallEdges(ctx, methodQname, callCode, effectiveName, edges, methodScope.localVars);
|
|
1207
|
+
const callLookup = makeTypeLookup([...paramDecls(m.params), ...collectDecls(callCode)], outerFields);
|
|
1208
|
+
emitInstanceCallEdges(ctx, methodQname, callCode, {
|
|
1209
|
+
lookup: callLookup,
|
|
1210
|
+
scope: methodScope,
|
|
1211
|
+
selfName: effectiveName,
|
|
1212
|
+
parentName,
|
|
1213
|
+
declared,
|
|
1214
|
+
fields: outerFields,
|
|
1215
|
+
}, edges, methodEdgeStart);
|
|
1216
|
+
emitNameRefEdges(ctx, methodQname, callCode, rawDecl, { lookup: callLookup, scope: methodScope }, edges, methodEdgeStart);
|
|
1217
|
+
emitClassRefEdges(ctx, methodQname, callCode, methodScope, edges);
|
|
1218
|
+
// The signature is outside `callCode`: prepend it so parameter and
|
|
1219
|
+
// return types count for the method too.
|
|
1220
|
+
const sig = `${m.returnType} ${m.name}(${m.params.join(", ")});\n`;
|
|
1221
|
+
emitTypeRefEdges(ctx, methodQname, sig + callCode, methodScope, edges, methodEdgeStart);
|
|
1222
|
+
emitDescribeRefEdges(ctx, methodQname, callCode, edges);
|
|
1223
|
+
const queries = rawDecl === null ? [] : foldDynamicSoql(rawDecl);
|
|
1224
|
+
methodQueries.push(...queries);
|
|
1225
|
+
setDynamicSoqlSkips(methodNode, emitDynamicSoqlEdges(ctx, methodQname, queries, edges, methodEdgeStart));
|
|
1226
|
+
}
|
|
1227
|
+
// Code outside method bodies (field initializers, constructors, static
|
|
1228
|
+
// blocks, method signatures) is attributed to the class itself.
|
|
1229
|
+
if (parserMode !== "ast") {
|
|
1230
|
+
const outside = maskedBody.split("");
|
|
1231
|
+
for (const d of declRanges) {
|
|
1232
|
+
for (let k = d.start; k < d.start + d.text.length; k++) {
|
|
1233
|
+
if (outside[k] !== "\n")
|
|
1234
|
+
outside[k] = " ";
|
|
422
1235
|
}
|
|
423
|
-
sc = scRe.exec(body);
|
|
424
1236
|
}
|
|
1237
|
+
const rest = outside.join("");
|
|
1238
|
+
emitStaticCallEdges(ctx, classQname, rest, effectiveName, edges, refScope.localVars);
|
|
1239
|
+
// Outside method bodies a name is typed only when every declaration of
|
|
1240
|
+
// it agrees (no positional scoping across constructors / initialisers).
|
|
1241
|
+
const restDecls = collectDecls(rest);
|
|
1242
|
+
const restLookup = makeTypeLookup([], restDecls);
|
|
1243
|
+
emitInstanceCallEdges(ctx, classQname, rest, {
|
|
1244
|
+
lookup: restLookup,
|
|
1245
|
+
scope: refScope,
|
|
1246
|
+
selfName: effectiveName,
|
|
1247
|
+
parentName,
|
|
1248
|
+
declared,
|
|
1249
|
+
fields: restDecls,
|
|
1250
|
+
}, edges);
|
|
1251
|
+
emitNameRefEdges(ctx, classQname, rest, input.body, { lookup: restLookup, scope: refScope }, edges);
|
|
1252
|
+
emitClassRefEdges(ctx, classQname, rest, refScope, edges);
|
|
1253
|
+
emitTypeRefEdges(ctx, classQname, rest, refScope, edges);
|
|
1254
|
+
emitDescribeRefEdges(ctx, classQname, rest, edges);
|
|
1255
|
+
// Dynamic SOQL outside method bodies (constants, constructors): every
|
|
1256
|
+
// folded query in the file minus those already attributed to a method.
|
|
1257
|
+
const left = new Map();
|
|
1258
|
+
for (const q of methodQueries)
|
|
1259
|
+
left.set(q, (left.get(q) ?? 0) + 1);
|
|
1260
|
+
const classQueries = foldDynamicSoql(input.body).filter((q) => {
|
|
1261
|
+
const n = left.get(q) ?? 0;
|
|
1262
|
+
if (n > 0)
|
|
1263
|
+
left.set(q, n - 1);
|
|
1264
|
+
return n === 0;
|
|
1265
|
+
});
|
|
1266
|
+
setDynamicSoqlSkips(nodes.find((n) => String(n.qualifiedName) === classQname), emitDynamicSoqlEdges(ctx, classQname, classQueries, edges, 0));
|
|
1267
|
+
// Inline SOQL outside method bodies (static field initialisers,
|
|
1268
|
+
// constructors, initializer blocks) belongs to the class.
|
|
1269
|
+
const restLoops = computeLoopRanges(rest);
|
|
1270
|
+
emitInlineSoqlEdges(ctx, classQname, rest, (idx) => restLoops.some((r) => idx > r.start && idx < r.end), edges, classPaths, classChildren);
|
|
425
1271
|
}
|
|
426
1272
|
// AST-mode (and shadow mode): walk the parsed compilationUnit and emit
|
|
427
1273
|
// edges that the regex pass cannot derive (real call arity, dotted field
|
|
@@ -438,13 +1284,33 @@ export class ApexClassParser {
|
|
|
438
1284
|
if (parserMode === "both") {
|
|
439
1285
|
// Tag AST edges so a shadow diff can distinguish overlap.
|
|
440
1286
|
for (const e of extraction.edges) {
|
|
441
|
-
e.attributes.
|
|
1287
|
+
e.attributes.callResolution = "ast-shadow";
|
|
442
1288
|
edges.push(e);
|
|
443
1289
|
}
|
|
444
1290
|
}
|
|
445
1291
|
else {
|
|
446
|
-
|
|
1292
|
+
// Member-access field edges were already emitted per method; the AST
|
|
1293
|
+
// dotted-ref / select-list READS_FIELD to the same (src, dst) would
|
|
1294
|
+
// only duplicate them.
|
|
1295
|
+
const fieldKeys = new Set(edges
|
|
1296
|
+
.filter((e) => e.relType === REL_TYPES.READS_FIELD || e.relType === REL_TYPES.WRITES_FIELD)
|
|
1297
|
+
.map((e) => `${e.relType}|${String(e.srcQualifiedName)}|${String(e.dstQualifiedName).toLowerCase()}`));
|
|
1298
|
+
for (const e of extraction.edges) {
|
|
1299
|
+
const key = `${e.relType}|${String(e.srcQualifiedName)}|${String(e.dstQualifiedName).toLowerCase()}`;
|
|
1300
|
+
if (fieldKeys.has(key))
|
|
1301
|
+
continue;
|
|
1302
|
+
if (e.relType === REL_TYPES.READS_FIELD || e.relType === REL_TYPES.WRITES_FIELD)
|
|
1303
|
+
fieldKeys.add(key);
|
|
447
1304
|
edges.push(e);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if (parserMode === "ast") {
|
|
1308
|
+
// "both" keeps the regex paths (regex is authoritative there).
|
|
1309
|
+
for (const [qn, paths] of extraction.relationshipPaths) {
|
|
1310
|
+
const target = methodNodes.get(qn) ?? nodes.find((n) => String(n.qualifiedName) === classQname);
|
|
1311
|
+
if (target)
|
|
1312
|
+
setRelationshipPaths(target, paths);
|
|
1313
|
+
}
|
|
448
1314
|
}
|
|
449
1315
|
if (process.env.SFGRAPH_DEBUG_INGEST === "1" && extraction.diagnostics.length > 0) {
|
|
450
1316
|
ctx.logger?.debug?.("apex ast diagnostics", {
|
|
@@ -462,16 +1328,51 @@ export class ApexClassParser {
|
|
|
462
1328
|
}
|
|
463
1329
|
// Test class linkage
|
|
464
1330
|
if (header.isTest) {
|
|
465
|
-
const { testTargets } = parseClassNameAndIsTest(cleaned);
|
|
1331
|
+
const { testTargets, lowerTargets } = parseClassNameAndIsTest(cleaned);
|
|
1332
|
+
// Lowercase-initial names (`new myController()`) are legal class
|
|
1333
|
+
// names but indistinguishable from variables here: keep them as
|
|
1334
|
+
// candidates the post-merge resolver matches against known classes.
|
|
1335
|
+
const candidates = [];
|
|
1336
|
+
for (const t of lowerTargets) {
|
|
1337
|
+
if (t.toLowerCase() === effectiveName.toLowerCase())
|
|
1338
|
+
continue;
|
|
1339
|
+
if (!isUserClassName(t, refScope) || refScope.localVars.has(t.toLowerCase()))
|
|
1340
|
+
continue;
|
|
1341
|
+
if (candidates.some((c) => c.toLowerCase() === t.toLowerCase()))
|
|
1342
|
+
continue;
|
|
1343
|
+
candidates.push(t);
|
|
1344
|
+
}
|
|
1345
|
+
const classNode = nodes.find((n) => String(n.qualifiedName) === classQname);
|
|
1346
|
+
if (classNode && candidates.length > 0)
|
|
1347
|
+
classNode.attributes.testTargetCandidates = candidates;
|
|
466
1348
|
for (const t of testTargets) {
|
|
467
1349
|
if (t === effectiveName)
|
|
468
1350
|
continue;
|
|
469
|
-
|
|
1351
|
+
// Only plausible user classes: not Apex system types/namespaces
|
|
1352
|
+
// (Database, System, Test, String), SObjects, types declared in this
|
|
1353
|
+
// file, or declared variables/fields used as call receivers.
|
|
1354
|
+
if (!isUserClassName(t, refScope) || refScope.localVars.has(t.toLowerCase()))
|
|
470
1355
|
continue;
|
|
471
1356
|
edges.push(makeEdge(ctx, classQname, REL_TYPES.IS_TEST_FOR, `ApexClass:${stripNs(t, ctx.namespace)}`));
|
|
472
1357
|
}
|
|
473
1358
|
}
|
|
474
|
-
|
|
1359
|
+
setChildRelationships(nodes.find((n) => String(n.qualifiedName) === classQname), classChildren);
|
|
1360
|
+
if (classPaths.length > 0) {
|
|
1361
|
+
const classNode = nodes.find((n) => String(n.qualifiedName) === classQname);
|
|
1362
|
+
if (classNode)
|
|
1363
|
+
setRelationshipPaths(classNode, classPaths);
|
|
1364
|
+
}
|
|
1365
|
+
return { nodes, edges: aggregateGovernorFacts(edges), snippets };
|
|
475
1366
|
}
|
|
476
1367
|
}
|
|
1368
|
+
/** Store relationship paths on a node (only when there are any). */
|
|
1369
|
+
function setRelationshipPaths(node, paths) {
|
|
1370
|
+
if (paths.length === 0)
|
|
1371
|
+
return;
|
|
1372
|
+
const attrs = node.attributes;
|
|
1373
|
+
const cur = attrs.apexRelationshipPaths ?? [];
|
|
1374
|
+
const merged = [...cur];
|
|
1375
|
+
mergePaths(merged, paths);
|
|
1376
|
+
attrs.apexRelationshipPaths = merged;
|
|
1377
|
+
}
|
|
477
1378
|
//# sourceMappingURL=class.js.map
|