homegraph 1.2.0 → 1.4.1
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/bin/homegraph.js +3 -0
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +85 -0
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +64 -5
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +178 -14
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +19 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +117 -2
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +299 -6
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +32 -1
- package/dist/db/wal-valve.d.ts +106 -0
- package/dist/db/wal-valve.d.ts.map +1 -0
- package/dist/db/wal-valve.js +208 -0
- package/dist/db/wal-valve.js.map +1 -0
- package/dist/directory.d.ts +9 -5
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +210 -19
- package/dist/directory.js.map +1 -1
- package/dist/extraction/cfml-extractor.d.ts +107 -0
- package/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/dist/extraction/cfml-extractor.js +494 -0
- package/dist/extraction/cfml-extractor.js.map +1 -0
- package/dist/extraction/grammars.d.ts +25 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +177 -24
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +43 -4
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +308 -5
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +42 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +369 -4
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/cfquery.d.ts +12 -0
- package/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/dist/extraction/languages/cfquery.js +28 -0
- package/dist/extraction/languages/cfquery.js.map +1 -0
- package/dist/extraction/languages/cfscript.d.ts +3 -0
- package/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/dist/extraction/languages/cfscript.js +73 -0
- package/dist/extraction/languages/cfscript.js.map +1 -0
- package/dist/extraction/languages/cobol.d.ts +33 -0
- package/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/dist/extraction/languages/cobol.js +499 -0
- package/dist/extraction/languages/cobol.js.map +1 -0
- package/dist/extraction/languages/erlang.d.ts +3 -0
- package/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/dist/extraction/languages/erlang.js +350 -0
- package/dist/extraction/languages/erlang.js.map +1 -0
- package/dist/extraction/languages/index.d.ts.map +1 -1
- package/dist/extraction/languages/index.js +16 -0
- package/dist/extraction/languages/index.js.map +1 -1
- package/dist/extraction/languages/nix.d.ts +3 -0
- package/dist/extraction/languages/nix.d.ts.map +1 -0
- package/dist/extraction/languages/nix.js +294 -0
- package/dist/extraction/languages/nix.js.map +1 -0
- package/dist/extraction/languages/solidity.d.ts +3 -0
- package/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/dist/extraction/languages/solidity.js +293 -0
- package/dist/extraction/languages/solidity.js.map +1 -0
- package/dist/extraction/languages/terraform.d.ts +3 -0
- package/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/dist/extraction/languages/terraform.js +641 -0
- package/dist/extraction/languages/terraform.js.map +1 -0
- package/dist/extraction/languages/vbnet.d.ts +11 -0
- package/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/dist/extraction/languages/vbnet.js +141 -0
- package/dist/extraction/languages/vbnet.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/dist/extraction/mybatis-extractor.js +140 -38
- package/dist/extraction/mybatis-extractor.js.map +1 -1
- package/dist/extraction/parse-pool.d.ts +27 -1
- package/dist/extraction/parse-pool.d.ts.map +1 -1
- package/dist/extraction/parse-pool.js +75 -7
- package/dist/extraction/parse-pool.js.map +1 -1
- package/dist/extraction/parse-worker.js +9 -2
- package/dist/extraction/parse-worker.js.map +1 -1
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/dist/extraction/tree-sitter-helpers.js +1 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/dist/extraction/tree-sitter-types.d.ts +3 -1
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +38 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +754 -10
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +1 -1
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +60 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +348 -5
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +42 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +97 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/daemon.d.ts +25 -3
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +62 -8
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/dist/mcp/dynamic-boundaries.js +2 -1
- package/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/dist/mcp/early-ppid.d.ts +26 -0
- package/dist/mcp/early-ppid.d.ts.map +1 -0
- package/dist/mcp/early-ppid.js +29 -0
- package/dist/mcp/early-ppid.js.map +1 -0
- package/dist/mcp/engine.d.ts +4 -5
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +27 -1
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +41 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/liveness-watchdog.d.ts +18 -1
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/dist/mcp/liveness-watchdog.js +73 -13
- package/dist/mcp/liveness-watchdog.js.map +1 -1
- package/dist/mcp/memory-budget.d.ts +30 -0
- package/dist/mcp/memory-budget.d.ts.map +1 -0
- package/dist/mcp/memory-budget.js +123 -0
- package/dist/mcp/memory-budget.js.map +1 -0
- package/dist/mcp/proxy.d.ts.map +1 -1
- package/dist/mcp/proxy.js +88 -4
- package/dist/mcp/proxy.js.map +1 -1
- package/dist/mcp/query-cache.d.ts +35 -4
- package/dist/mcp/query-cache.d.ts.map +1 -1
- package/dist/mcp/query-cache.js +89 -19
- package/dist/mcp/query-cache.js.map +1 -1
- package/dist/mcp/query-pool.d.ts +46 -12
- package/dist/mcp/query-pool.d.ts.map +1 -1
- package/dist/mcp/query-pool.js +100 -23
- package/dist/mcp/query-pool.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +4 -27
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +39 -84
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/session.d.ts +14 -0
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +28 -1
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/startup-handshake.d.ts +44 -0
- package/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/dist/mcp/startup-handshake.js +73 -0
- package/dist/mcp/startup-handshake.js.map +1 -0
- package/dist/mcp/tools.d.ts +104 -4
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +2390 -191
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/project-config.d.ts +38 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +101 -2
- package/dist/project-config.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +178 -146
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts +9 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +1164 -176
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/frameworks/cics.d.ts +20 -0
- package/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/dist/resolution/frameworks/cics.js +90 -0
- package/dist/resolution/frameworks/cics.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +3 -0
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/dist/resolution/frameworks/java.js +14 -6
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/dist/resolution/frameworks/terraform.js +277 -0
- package/dist/resolution/frameworks/terraform.js.map +1 -0
- package/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/dist/resolution/goframe-synthesizer.js +8 -3
- package/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts +7 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +129 -4
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +60 -7
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +430 -80
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +0 -3
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +354 -33
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/strip-comments.d.ts +1 -1
- package/dist/resolution/strip-comments.d.ts.map +1 -1
- package/dist/resolution/strip-comments.js +49 -0
- package/dist/resolution/strip-comments.js.map +1 -1
- package/dist/resolution/types.d.ts +29 -0
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.d.ts +10 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.js +142 -4
- package/dist/resolution/workspace-packages.js.map +1 -1
- package/dist/search/identifier-segments.d.ts +60 -0
- package/dist/search/identifier-segments.d.ts.map +1 -0
- package/dist/search/identifier-segments.js +176 -0
- package/dist/search/identifier-segments.js.map +1 -0
- package/dist/search/query-utils.d.ts +201 -0
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +919 -2
- package/dist/search/query-utils.js.map +1 -1
- package/dist/sync/git-hooks.d.ts.map +1 -1
- package/dist/sync/git-hooks.js +2 -0
- package/dist/sync/git-hooks.js.map +1 -1
- package/dist/sync/watcher.d.ts +10 -5
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +51 -14
- package/dist/sync/watcher.js.map +1 -1
- package/dist/sync/worktree.d.ts.map +1 -1
- package/dist/sync/worktree.js +5 -0
- package/dist/sync/worktree.js.map +1 -1
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/dist/upgrade/index.d.ts +32 -0
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +162 -14
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/remove-binary.d.ts +87 -0
- package/dist/upgrade/remove-binary.d.ts.map +1 -0
- package/dist/upgrade/remove-binary.js +289 -0
- package/dist/upgrade/remove-binary.js.map +1 -0
- package/dist/upgrade/update-check.d.ts +92 -0
- package/dist/upgrade/update-check.d.ts.map +1 -0
- package/dist/upgrade/update-check.js +258 -0
- package/dist/upgrade/update-check.js.map +1 -0
- package/package.json +2 -2
- package/scripts/agent-eval/run-all.sh +6 -0
- package/scripts/build-bundle.sh +5 -0
- package/scripts/npm-shim.js +8 -1
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/data/agents.json +0 -109
- package/scripts/exp_boundary_eval/data/experiments.json +0 -140
- package/scripts/qa_eval/README.md +0 -407
- package/scripts/qa_eval/_test_deveco_probe.py +0 -41
- package/scripts/qa_eval/agent_runner.py +0 -526
- package/scripts/qa_eval/data/.gitignore +0 -4
- package/scripts/qa_eval/data/test-set.jsonl +0 -2
- package/scripts/qa_eval/eval_metrics.py +0 -274
- package/scripts/qa_eval/external_agent.py +0 -976
- package/scripts/qa_eval/llm_config.py +0 -92
- package/scripts/qa_eval/memory_monitor.py +0 -132
- package/scripts/qa_eval/my_answer_accuracy.py +0 -187
- package/scripts/qa_eval/requirements.txt +0 -2
- package/scripts/qa_eval/run_pipeline.py +0 -804
- package/scripts/qa_eval/stats_efficiency.py +0 -279
- package/scripts/qa_eval/stats_scores.py +0 -207
|
@@ -140,7 +140,9 @@ function pickClosestFileNode(candidates, ref) {
|
|
|
140
140
|
const LANGUAGE_FAMILY = {
|
|
141
141
|
java: 'jvm', kotlin: 'jvm', scala: 'jvm',
|
|
142
142
|
swift: 'apple', objc: 'apple',
|
|
143
|
-
|
|
143
|
+
// ArkTS is a TS superset — every HarmonyOS project mixes `.ets` UI with
|
|
144
|
+
// `.ts` logic modules, so refs must cross freely between them.
|
|
145
|
+
typescript: 'web', tsx: 'web', javascript: 'web', jsx: 'web', arkts: 'web',
|
|
144
146
|
c: 'c', cpp: 'c',
|
|
145
147
|
// Razor/Blazor markup names C# types — same family so `@model Foo` /
|
|
146
148
|
// `<MyComponent/>` resolve to their `.cs` class through the cross-family gate.
|
|
@@ -222,6 +224,7 @@ function matchFunctionRef(ref, context) {
|
|
|
222
224
|
// implicit-self, Java/Kotlin method references.
|
|
223
225
|
const bareFnOnly = ref.language === 'typescript' || ref.language === 'tsx' ||
|
|
224
226
|
ref.language === 'javascript' || ref.language === 'jsx' ||
|
|
227
|
+
ref.language === 'arkts' ||
|
|
225
228
|
ref.language === 'cpp' || ref.language === 'python' ||
|
|
226
229
|
ref.language === 'php';
|
|
227
230
|
// Qualified member-pointer (`&Widget::on_click` → "Widget::on_click"):
|
|
@@ -384,7 +387,17 @@ function matchByQualifiedName(ref, context) {
|
|
|
384
387
|
if (!ref.referenceName.includes('::') && !ref.referenceName.includes('.')) {
|
|
385
388
|
return null;
|
|
386
389
|
}
|
|
387
|
-
|
|
390
|
+
// A method call `receiver.method()` can share an exact qualified name with a
|
|
391
|
+
// config-file key: `service.process()` (a `calls` ref named `service.process`)
|
|
392
|
+
// vs the yaml key `service.process`. Config keys are bound to their code refs
|
|
393
|
+
// upstream by the framework resolvers (`@Value` → `references`); a `calls` ref
|
|
394
|
+
// must never resolve to a yaml/properties config node — that's a wrong edge
|
|
395
|
+
// AND it hides the real callee. Drop those from both the exact and the partial
|
|
396
|
+
// candidate sets so resolution falls through to method resolution below (#1180).
|
|
397
|
+
const keepForRef = (nodes) => ref.referenceKind === 'calls'
|
|
398
|
+
? nodes.filter((n) => !(n.kind === 'constant' && (n.language === 'yaml' || n.language === 'properties')))
|
|
399
|
+
: nodes;
|
|
400
|
+
const candidates = keepForRef(context.getNodesByQualifiedName(ref.referenceName));
|
|
388
401
|
if (candidates.length === 1) {
|
|
389
402
|
return {
|
|
390
403
|
original: ref,
|
|
@@ -413,8 +426,7 @@ function matchByQualifiedName(ref, context) {
|
|
|
413
426
|
const parts = ref.referenceName.split(/[:.]/);
|
|
414
427
|
const lastName = parts[parts.length - 1];
|
|
415
428
|
if (lastName) {
|
|
416
|
-
const partialCandidates = context
|
|
417
|
-
.getNodesByName(lastName)
|
|
429
|
+
const partialCandidates = keepForRef(context.getNodesByName(lastName))
|
|
418
430
|
.filter((candidate) => candidate.qualifiedName.endsWith(ref.referenceName));
|
|
419
431
|
const chosen = preferCallSiteFile(partialCandidates, ref.filePath)[0];
|
|
420
432
|
if (chosen) {
|
|
@@ -468,17 +480,28 @@ depth = 0) {
|
|
|
468
480
|
// in-class (`class Foo { int bar() { ... } }`) or out-of-line in a separate
|
|
469
481
|
// file (`int Foo::bar() { ... }` in foo.cpp while class Foo is in foo.hpp).
|
|
470
482
|
// The previous same-file approach missed the latter — the typical C++ layout.
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
483
|
+
// Prefer the context's per-(type, method) memo: the raw name lookup fetches
|
|
484
|
+
// EVERY node sharing the method name — tens of thousands of rows for a
|
|
485
|
+
// collision-heavy Java name like `execute` — and re-filtering that per ref
|
|
486
|
+
// was a dominant term in the #1122 watchdog kill on large repos. Only the
|
|
487
|
+
// ref-independent filter is memoized; per-ref disambiguation stays below.
|
|
488
|
+
let matches;
|
|
489
|
+
if (context.getMethodMatches) {
|
|
490
|
+
matches = context.getMethodMatches(typeName, methodName, ref.language);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
const methodCandidates = context.getNodesByName(methodName);
|
|
494
|
+
const want = `${typeName}::${methodName}`;
|
|
495
|
+
matches = [];
|
|
496
|
+
for (const m of methodCandidates) {
|
|
497
|
+
if (m.kind !== 'method')
|
|
498
|
+
continue;
|
|
499
|
+
if (m.language !== ref.language)
|
|
500
|
+
continue;
|
|
501
|
+
const qn = m.qualifiedName;
|
|
502
|
+
if (qn === want || qn.endsWith(`::${want}`)) {
|
|
503
|
+
matches.push(m);
|
|
504
|
+
}
|
|
482
505
|
}
|
|
483
506
|
}
|
|
484
507
|
if (matches.length === 0) {
|
|
@@ -565,10 +588,14 @@ function buildDeclaratorRegex(escapedReceiver) {
|
|
|
565
588
|
return new RegExp(`([A-Za-z_][\\w:]*(?:\\s*<[^;=(){}]+>)?(?:\\s*[*&]+)?)\\s*\\b${escapedReceiver}\\b\\s*(?=[;=,)\\[{(]|$)`);
|
|
566
589
|
}
|
|
567
590
|
function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
568
|
-
|
|
569
|
-
|
|
591
|
+
// Per-file lines cache when available — this runs per `receiver->method()`
|
|
592
|
+
// ref and re-splitting the file each time is the same quadratic as the
|
|
593
|
+
// shared inferrer's (#1122).
|
|
594
|
+
const lines = context.getFileLines
|
|
595
|
+
? context.getFileLines(ref.filePath)
|
|
596
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
597
|
+
if (!lines || lines.length === 0)
|
|
570
598
|
return null;
|
|
571
|
-
const lines = source.split(/\r?\n/);
|
|
572
599
|
const callLineIndex = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
573
600
|
const escapedReceiver = receiverName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
574
601
|
const receiverPattern = new RegExp(`\\b${escapedReceiver}\\b`);
|
|
@@ -601,10 +628,12 @@ function inferCppReceiverType(receiverName, ref, context, depth = 0) {
|
|
|
601
628
|
for (const headerPath of headerCandidates) {
|
|
602
629
|
if (!context.fileExists(headerPath))
|
|
603
630
|
continue;
|
|
604
|
-
const
|
|
605
|
-
|
|
631
|
+
const headerLines = context.getFileLines
|
|
632
|
+
? context.getFileLines(headerPath)
|
|
633
|
+
: (context.readFile(headerPath)?.split(/\r?\n/) ?? null);
|
|
634
|
+
if (!headerLines)
|
|
606
635
|
continue;
|
|
607
|
-
for (const line of
|
|
636
|
+
for (const line of headerLines) {
|
|
608
637
|
if (!receiverPattern.test(line))
|
|
609
638
|
continue;
|
|
610
639
|
const declaratorMatch = line.match(declaratorRegex);
|
|
@@ -975,9 +1004,17 @@ function localReceiverTypePatterns(language, r) {
|
|
|
975
1004
|
case 'javascript':
|
|
976
1005
|
case 'tsx':
|
|
977
1006
|
case 'jsx':
|
|
1007
|
+
case 'arkts':
|
|
978
1008
|
return [
|
|
979
1009
|
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_$][\\w.$]*)`), // = new Logger()
|
|
980
|
-
|
|
1010
|
+
// No keyword requirement, so this matches BOTH a local annotation
|
|
1011
|
+
// (`const lg: Logger`) and a typed parameter (`function use(lg: Logger)`
|
|
1012
|
+
// / `(lg: Logger) =>`) — the parameter case the old `const|let|var`
|
|
1013
|
+
// prefix excluded (#1125). Mirrors Kotlin/Swift/Scala; the capture stops
|
|
1014
|
+
// at `<` so a generic-typed param (`repo: Repository<User>`) still yields
|
|
1015
|
+
// `Repository`. resolveMethodOnType validates the type actually declares
|
|
1016
|
+
// the method, so the looser match produces no edge on a mis-inference.
|
|
1017
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.$]*)`), // lg: Logger (annotation or typed param)
|
|
981
1018
|
];
|
|
982
1019
|
case 'python':
|
|
983
1020
|
return [
|
|
@@ -1007,12 +1044,21 @@ function localReceiverTypePatterns(language, r) {
|
|
|
1007
1044
|
case 'rust':
|
|
1008
1045
|
return [
|
|
1009
1046
|
new RegExp(`\\blet\\s+(?:mut\\s+)?${r}\\b(?:\\s*:[^=]+)?=\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // let lg = Logger::new()/Logger{}/Logger
|
|
1010
|
-
|
|
1047
|
+
// No `let`, so this covers a `let lg: Logger` binding AND a typed
|
|
1048
|
+
// parameter (`fn use(lg: &Logger)`, a closure `|lg: Logger|`) — the
|
|
1049
|
+
// parameter case the old `let`-anchored pattern excluded (#1125).
|
|
1050
|
+
new RegExp(`\\b${r}\\s*:\\s*&?(?:mut\\s+)?([A-Z][\\w]*)`), // lg: Logger (binding or typed param)
|
|
1011
1051
|
];
|
|
1012
1052
|
case 'go':
|
|
1013
1053
|
return [
|
|
1014
1054
|
new RegExp(`\\b${r}\\b\\s*:=\\s*&?([A-Za-z_][\\w.]*)\\s*{`), // lg := Logger{} / &Logger{}
|
|
1015
1055
|
new RegExp(`\\bvar\\s+${r}\\s+\\*?([A-Za-z_][\\w.]*)`), // var lg Logger / *Logger
|
|
1056
|
+
// A typed parameter / method receiver (`func use(lg Logger)`,
|
|
1057
|
+
// `func (l Logger) M()`) — name-before-type with no `var`/`:=` (#1125).
|
|
1058
|
+
// PascalCase-guarded (unlike the anchored patterns above) to keep the
|
|
1059
|
+
// keyword-free `ident Type` shape from matching unrelated pairs; the
|
|
1060
|
+
// enclosing-scope bound already excludes package-level struct fields.
|
|
1061
|
+
new RegExp(`\\b${r}\\s+\\*?([A-Z][\\w.]*)`), // func use(lg Logger) / (l Logger)
|
|
1016
1062
|
];
|
|
1017
1063
|
case 'ruby':
|
|
1018
1064
|
return [
|
|
@@ -1026,18 +1072,35 @@ function localReceiverTypePatterns(language, r) {
|
|
|
1026
1072
|
case 'dart':
|
|
1027
1073
|
return [
|
|
1028
1074
|
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w.]*)\\s*\\(`), // var lg = Logger(...)
|
|
1029
|
-
|
|
1075
|
+
// Trailing `[=;,)]` (not just `[=;]`) so a typed parameter — `Logger lg)`
|
|
1076
|
+
// / `Logger lg,` — matches too, not only `Logger lg = ...` / `Logger lg;`
|
|
1077
|
+
// (#1125). Mirrors Java/C#.
|
|
1078
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`), // Logger lg = ... / param
|
|
1030
1079
|
];
|
|
1031
1080
|
case 'php':
|
|
1032
1081
|
return [
|
|
1033
1082
|
new RegExp(`\\$?${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $lg = new Logger()
|
|
1083
|
+
// A typed parameter (`function use(Logger $lg)`, `?Logger $lg`,
|
|
1084
|
+
// `\\App\\Logger $lg`, `&$lg` by-ref) and a typed `catch (E $e)` — the
|
|
1085
|
+
// type sits before the `$`-variable (#1125). Namespace `\\` allowed.
|
|
1086
|
+
new RegExp(`\\b([A-Za-z_\\\\][\\w\\\\]*)\\s+&?\\$${r}\\b`), // Logger $lg (typed param)
|
|
1034
1087
|
];
|
|
1035
1088
|
case 'lua':
|
|
1036
1089
|
case 'luau':
|
|
1037
1090
|
return [
|
|
1038
1091
|
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\.new\\b`), // local lg = Logger.new()
|
|
1039
1092
|
new RegExp(`\\b${r}\\b\\s*=\\s*([A-Z][\\w]*)\\s*\\(`), // local lg = Logger(...) (callable table)
|
|
1040
|
-
|
|
1093
|
+
// Luau annotation (`local lg: Logger`) / typed param — but Lua's
|
|
1094
|
+
// method-call syntax is the IDENTICAL `receiver:Name` shape, and the
|
|
1095
|
+
// backward scan starts on the call's own line, so without a gate any
|
|
1096
|
+
// PascalCase method call (`lg:Log()`, the Roblox convention)
|
|
1097
|
+
// self-matches as "type = Log" before the scan reaches the real
|
|
1098
|
+
// declaration (#1124). The lookahead rejects a capture followed by
|
|
1099
|
+
// any of Lua's three call forms — `(args)`, `"s"`/`'s'`/`[[s]]`,
|
|
1100
|
+
// `{t}` — and its leading `[\w.]` alternative stops backtracking from
|
|
1101
|
+
// shrinking the capture to dodge the gate (`lg:Log()` would otherwise
|
|
1102
|
+
// still match, as `Lo`).
|
|
1103
|
+
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w.]*)(?![\\w.]|\\s*[({"'\\[])`), // local lg: Logger / typed param
|
|
1041
1104
|
];
|
|
1042
1105
|
case 'r':
|
|
1043
1106
|
return [
|
|
@@ -1048,6 +1111,36 @@ function localReceiverTypePatterns(language, r) {
|
|
|
1048
1111
|
new RegExp(`\\b${r}\\b\\s*:\\s*([A-Z][\\w]*)`), // var lg: TLogger / param lg: TLogger
|
|
1049
1112
|
new RegExp(`\\b${r}\\b\\s*:=\\s*([A-Z][\\w.]*)\\.Create\\b`), // lg := TLogger.Create
|
|
1050
1113
|
];
|
|
1114
|
+
case 'cfml':
|
|
1115
|
+
case 'cfscript':
|
|
1116
|
+
return [
|
|
1117
|
+
// svc = new UserService() / new path.to.UserService() — dotted component
|
|
1118
|
+
// paths reduce to their final segment via normalizeInferredTypeName.
|
|
1119
|
+
// Also matches inside tag markup (`<cfset svc = new UserService()>`)
|
|
1120
|
+
// since the scan reads raw source lines.
|
|
1121
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*new\\s+([A-Za-z_][\\w.]*)`),
|
|
1122
|
+
// The classic form: svc = createObject("component", "path.to.UserService")
|
|
1123
|
+
// (casing of createObject varies in the wild), plus the modern
|
|
1124
|
+
// single-argument form createObject("path.to.UserService").
|
|
1125
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']component["']\\s*,\\s*["']([\\w.]+)["']`),
|
|
1126
|
+
new RegExp(`\\b${r}\\b\\s*=\\s*[Cc]reate[Oo]bject\\s*\\(\\s*["']([\\w.]+)["']\\s*\\)`),
|
|
1127
|
+
// Typed cfscript parameter: `function save(UserService svc)` /
|
|
1128
|
+
// `required UserService svc` — CFML's built-in types (string, numeric,
|
|
1129
|
+
// any, struct…) are lowercase by convention, so the PascalCase guard
|
|
1130
|
+
// excludes them.
|
|
1131
|
+
new RegExp(`\\b([A-Z][\\w.]*)\\s+${r}\\b\\s*[=;,)]`),
|
|
1132
|
+
// Tag-form typed argument, either attribute order:
|
|
1133
|
+
// <cfargument name="svc" type="path.to.UserService">
|
|
1134
|
+
new RegExp(`\\bcfargument[^>\\n]*\\bname\\s*=\\s*["']${r}["'][^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1135
|
+
new RegExp(`\\bcfargument[^>\\n]*\\btype\\s*=\\s*["']([\\w.]+)["'][^>\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1136
|
+
// Component property (incl. WireBox DI): `property name="svc"
|
|
1137
|
+
// inject="UserService";` / `<cfproperty name="svc" type="UserService">`,
|
|
1138
|
+
// either attribute order. An inject DSL value with a namespace
|
|
1139
|
+
// (`inject="svc@core"`) captures only the leading name and simply
|
|
1140
|
+
// fails type-validation — no edge, never a wrong one.
|
|
1141
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\bname\\s*=\\s*["']${r}["'][^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["']`, 'i'),
|
|
1142
|
+
new RegExp(`\\b(?:cf)?property\\b[^;\\n]*\\b(?:type|inject)\\s*=\\s*["']([\\w.]+)["'][^;\\n]*\\bname\\s*=\\s*["']${r}["']`, 'i'),
|
|
1143
|
+
];
|
|
1051
1144
|
default:
|
|
1052
1145
|
return [];
|
|
1053
1146
|
}
|
|
@@ -1074,20 +1167,70 @@ function enclosingScopeStartLine(ref, context) {
|
|
|
1074
1167
|
* scope so a same-named variable in another function can't leak in.
|
|
1075
1168
|
*/
|
|
1076
1169
|
function inferLocalReceiverType(receiverName, ref, context) {
|
|
1077
|
-
|
|
1170
|
+
// CFML scope prefixes: `variables.svc` / `this.svc` name a COMPONENT-scoped
|
|
1171
|
+
// field whose assignment or `property` declaration usually lives outside the
|
|
1172
|
+
// calling function (the init-pseudoconstructor / WireBox-injection pattern),
|
|
1173
|
+
// and `local.svc` is an explicit function-local. Strip the prefix so the
|
|
1174
|
+
// declaration patterns match (`variables.svc = new X()`, `property
|
|
1175
|
+
// name="svc" …`, `var svc = …` all bind the bare name), and widen the scan
|
|
1176
|
+
// to the whole file for the component-scoped forms — nearest-declaration-
|
|
1177
|
+
// backward still wins, so a function-local shadowing the field is preferred.
|
|
1178
|
+
let scanReceiver = receiverName;
|
|
1179
|
+
let componentScoped = false;
|
|
1180
|
+
if (ref.language === 'cfml' || ref.language === 'cfscript') {
|
|
1181
|
+
const scoped = receiverName.match(/^(variables|this|local|arguments)\.(.+)$/i);
|
|
1182
|
+
if (scoped) {
|
|
1183
|
+
scanReceiver = scoped[2];
|
|
1184
|
+
const scope = scoped[1].toLowerCase();
|
|
1185
|
+
componentScoped = scope === 'variables' || scope === 'this';
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
// PHP `$this->prop` receiver — the property's declaration lives outside the
|
|
1189
|
+
// calling method (a promoted constructor parameter `private readonly Foo $prop`,
|
|
1190
|
+
// a typed property `private Foo $prop;`, or a classic constructor parameter
|
|
1191
|
+
// `Foo $prop` assigned in __construct). Strip the prefix and widen the scan to
|
|
1192
|
+
// the whole file (the constructor may sit below the calling method), but —
|
|
1193
|
+
// unlike CFML's scopes above — switch to PROPERTY-shaped patterns: a plain
|
|
1194
|
+
// `$prop` local or parameter lives in a different namespace than `$this->prop`
|
|
1195
|
+
// and can never shadow it, so the generic local patterns would type the
|
|
1196
|
+
// property from unrelated same-named variables in other methods (a wrong
|
|
1197
|
+
// 0.9-confidence edge, not a missing one).
|
|
1198
|
+
let phpProperty = false;
|
|
1199
|
+
if (ref.language === 'php') {
|
|
1200
|
+
const scoped = receiverName.match(/^this->(.+)$/);
|
|
1201
|
+
if (scoped) {
|
|
1202
|
+
scanReceiver = scoped[1];
|
|
1203
|
+
componentScoped = true;
|
|
1204
|
+
phpProperty = true;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
const escapedReceiver = scanReceiver.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1208
|
+
const patterns = phpProperty
|
|
1209
|
+
? phpPropertyTypePatterns(escapedReceiver)
|
|
1210
|
+
: localReceiverTypePatterns(ref.language, escapedReceiver);
|
|
1078
1211
|
if (patterns.length === 0)
|
|
1079
1212
|
return null;
|
|
1080
|
-
|
|
1081
|
-
|
|
1213
|
+
// Split through the context's per-file lines cache when available: this runs
|
|
1214
|
+
// for EVERY `receiver.method()` ref, and re-splitting the whole file per ref
|
|
1215
|
+
// was ~20% of total index CPU on Java-heavy repos (#1122).
|
|
1216
|
+
const lines = context.getFileLines
|
|
1217
|
+
? context.getFileLines(ref.filePath)
|
|
1218
|
+
: (context.readFile(ref.filePath)?.split(/\r?\n/) ?? null);
|
|
1219
|
+
if (!lines || lines.length === 0)
|
|
1082
1220
|
return null;
|
|
1083
|
-
const lines = source.split(/\r?\n/);
|
|
1084
1221
|
const callIdx = Math.max(0, Math.min(lines.length - 1, ref.line - 1));
|
|
1085
|
-
const startIdx =
|
|
1086
|
-
|
|
1087
|
-
|
|
1222
|
+
const startIdx = componentScoped
|
|
1223
|
+
? 0
|
|
1224
|
+
: Math.max(0, enclosingScopeStartLine(ref, context) - 1);
|
|
1225
|
+
const matchLine = (i) => {
|
|
1088
1226
|
const line = lines[i];
|
|
1089
1227
|
if (!line)
|
|
1090
|
-
|
|
1228
|
+
return null;
|
|
1229
|
+
// A generated/minified line (one multi-KB statement) is not something a
|
|
1230
|
+
// human-written local declaration lives on, and regexing it per ref is
|
|
1231
|
+
// pure waste — skip it rather than scan it.
|
|
1232
|
+
if (line.length > 10_000)
|
|
1233
|
+
return null;
|
|
1091
1234
|
for (const re of patterns) {
|
|
1092
1235
|
const m = line.match(re);
|
|
1093
1236
|
if (m && m[1]) {
|
|
@@ -1096,6 +1239,113 @@ function inferLocalReceiverType(receiverName, ref, context) {
|
|
|
1096
1239
|
return type;
|
|
1097
1240
|
}
|
|
1098
1241
|
}
|
|
1242
|
+
return null;
|
|
1243
|
+
};
|
|
1244
|
+
// Nearest declaration wins: scan backward from the call to the scope start.
|
|
1245
|
+
for (let i = callIdx; i >= startIdx; i--) {
|
|
1246
|
+
const type = matchLine(i);
|
|
1247
|
+
if (type)
|
|
1248
|
+
return type;
|
|
1249
|
+
}
|
|
1250
|
+
// A component-scoped field's declaration is position-independent — the
|
|
1251
|
+
// `variables.svc = new X()` pseudoconstructor assignment or `property`
|
|
1252
|
+
// declaration may sit BELOW the calling function in the file — so when the
|
|
1253
|
+
// backward pass finds nothing, sweep the remainder of the file too.
|
|
1254
|
+
if (componentScoped) {
|
|
1255
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
1256
|
+
const type = matchLine(i);
|
|
1257
|
+
if (type)
|
|
1258
|
+
return type;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
// A PHP property with no statically-typed declaration (classic pre-7.4
|
|
1262
|
+
// style) may still be typed by what gets ASSIGNED to it — follow the
|
|
1263
|
+
// `$this->prop = $var` assignment to the assigned variable's own typed
|
|
1264
|
+
// declaration (a classic or multi-line constructor parameter, or a typed
|
|
1265
|
+
// setter's parameter).
|
|
1266
|
+
if (phpProperty) {
|
|
1267
|
+
return inferPhpAssignedPropertyType(escapedReceiver, lines, callIdx);
|
|
1268
|
+
}
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Patterns that recover a PHP class property's declared type for a
|
|
1273
|
+
* `$this->prop` receiver. Deliberately NOT localReceiverTypePatterns: only
|
|
1274
|
+
* property-shaped declarations qualify —
|
|
1275
|
+
* 1. a modifier-prefixed typed declaration, which covers both a typed
|
|
1276
|
+
* property (`private ?Foo $prop;`) and a promoted constructor parameter
|
|
1277
|
+
* (`private readonly Foo $prop`), and
|
|
1278
|
+
* 2. the pseudoconstructor assignment (`$this->prop = new Foo(...)`).
|
|
1279
|
+
* A bare `X $prop` parameter or `$prop = new X()` local elsewhere in the
|
|
1280
|
+
* file must NOT match: those variables can never alias `$this->prop`.
|
|
1281
|
+
* Union-typed properties (`Foo|Bar $prop`) yield no match and thus no edge —
|
|
1282
|
+
* silent beats wrong. The classic untyped-property-assigned-in-constructor
|
|
1283
|
+
* shape is handled by inferPhpAssignedPropertyType instead.
|
|
1284
|
+
*/
|
|
1285
|
+
function phpPropertyTypePatterns(r) {
|
|
1286
|
+
return [
|
|
1287
|
+
new RegExp(`\\b(?:(?:private|protected|public|readonly|static|final)(?:\\(set\\))?\\s+)+\\??([A-Za-z_\\\\][\\w\\\\]*)\\s+&?\\$${r}\\b`), // private readonly ?Foo $prop (typed property / promoted param)
|
|
1288
|
+
new RegExp(`\\$this->${r}\\b\\s*=\\s*new\\s+([A-Za-z_\\\\][\\w\\\\]*)`), // $this->prop = new Foo()
|
|
1289
|
+
];
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Second-chance typing for a PHP `$this->prop` receiver whose property
|
|
1293
|
+
* declaration carries no static type (classic pre-7.4 style): find the
|
|
1294
|
+
* `$this->prop = $var` assignment, then recover `$var`'s type from its own
|
|
1295
|
+
* declaration WITHIN the assignment's function — the constructor's (possibly
|
|
1296
|
+
* multi-line) parameter list, a typed setter's parameter, or a `= new X()`
|
|
1297
|
+
* local. The backward scan stops at the enclosing `function` line (checked
|
|
1298
|
+
* for a match first — a single-line `__construct(Foo $var) { ... }` carries
|
|
1299
|
+
* the typed parameter itself), so a same-named variable in another method
|
|
1300
|
+
* can never type the property.
|
|
1301
|
+
*/
|
|
1302
|
+
function inferPhpAssignedPropertyType(escapedProp, lines, callIdx) {
|
|
1303
|
+
const assignRe = new RegExp(`\\$this->${escapedProp}\\b\\s*=\\s*\\$(\\w+)\\b`);
|
|
1304
|
+
const assignAt = (i) => {
|
|
1305
|
+
const line = lines[i];
|
|
1306
|
+
if (!line || line.length > 10_000)
|
|
1307
|
+
return null;
|
|
1308
|
+
return line.match(assignRe);
|
|
1309
|
+
};
|
|
1310
|
+
// The assignment is position-independent relative to the call — nearest-
|
|
1311
|
+
// backward first, then sweep forward, same order as the componentScoped scan.
|
|
1312
|
+
let assignIdx = -1;
|
|
1313
|
+
let varName = null;
|
|
1314
|
+
for (let i = callIdx; i >= 0; i--) {
|
|
1315
|
+
const m = assignAt(i);
|
|
1316
|
+
if (m) {
|
|
1317
|
+
assignIdx = i;
|
|
1318
|
+
varName = m[1];
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
if (varName === null) {
|
|
1323
|
+
for (let i = callIdx + 1; i < lines.length; i++) {
|
|
1324
|
+
const m = assignAt(i);
|
|
1325
|
+
if (m) {
|
|
1326
|
+
assignIdx = i;
|
|
1327
|
+
varName = m[1];
|
|
1328
|
+
break;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
if (varName === null)
|
|
1333
|
+
return null;
|
|
1334
|
+
const varPatterns = localReceiverTypePatterns('php', varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
1335
|
+
for (let i = assignIdx; i >= 0; i--) {
|
|
1336
|
+
const line = lines[i];
|
|
1337
|
+
if (line && line.length <= 10_000) {
|
|
1338
|
+
for (const re of varPatterns) {
|
|
1339
|
+
const m = line.match(re);
|
|
1340
|
+
if (m && m[1]) {
|
|
1341
|
+
const type = normalizeInferredTypeName(m[1]);
|
|
1342
|
+
if (type)
|
|
1343
|
+
return type;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
if (line && /\bfunction\b/.test(line))
|
|
1348
|
+
break;
|
|
1099
1349
|
}
|
|
1100
1350
|
return null;
|
|
1101
1351
|
}
|
|
@@ -1124,6 +1374,24 @@ function matchMethodCall(ref, context) {
|
|
|
1124
1374
|
const rDollarMatch = ref.language === 'r'
|
|
1125
1375
|
? ref.referenceName.match(/^([\w.]+)\$(\w+)$/)
|
|
1126
1376
|
: null;
|
|
1377
|
+
// PHP property receiver: `$this->prop->method()` reaches the resolver as
|
|
1378
|
+
// `this->prop.method` (the extractor records the receiver's raw text with the
|
|
1379
|
+
// leading `$` stripped). Resolve it EXCLUSIVELY through declared-type
|
|
1380
|
+
// inference + resolveMethodOnType validation — the name-similarity strategies
|
|
1381
|
+
// below must never see this shape, so a property whose type can't be
|
|
1382
|
+
// recovered stays unlinked rather than guessed (a wrong inference produces no
|
|
1383
|
+
// edge rather than a wrong one). Deeper chains (`this->a->b.method`) don't
|
|
1384
|
+
// match the single-property pattern and stay unlinked, same as before.
|
|
1385
|
+
const phpThisPropMatch = ref.language === 'php'
|
|
1386
|
+
? ref.referenceName.match(/^(this->\w+)\.(\w+)$/)
|
|
1387
|
+
: null;
|
|
1388
|
+
if (phpThisPropMatch) {
|
|
1389
|
+
const [, receiver, phpMethodName] = phpThisPropMatch;
|
|
1390
|
+
const inferredType = inferLocalReceiverType(receiver, ref, context);
|
|
1391
|
+
if (!inferredType)
|
|
1392
|
+
return null;
|
|
1393
|
+
return resolveMethodOnType(inferredType, phpMethodName, ref, context, 0.9, 'instance-method', importedFqnOf(inferredType, ref, context));
|
|
1394
|
+
}
|
|
1127
1395
|
const match = dotMatch || colonMatch || luaColonMatch || rDollarMatch;
|
|
1128
1396
|
if (!match) {
|
|
1129
1397
|
return null;
|
|
@@ -1438,6 +1706,8 @@ function matchFuzzy(ref, context) {
|
|
|
1438
1706
|
/**
|
|
1439
1707
|
* Match all strategies in order of confidence
|
|
1440
1708
|
*/
|
|
1709
|
+
/** ArkUI attribute-helper decorators a `.attr(...)` chain may resolve to. */
|
|
1710
|
+
const ARKUI_ATTRIBUTE_DECORATORS = new Set(['Extend', 'Styles', 'AnimatableExtend', 'Builder']);
|
|
1441
1711
|
function matchReference(ref, context) {
|
|
1442
1712
|
// Function-as-value refs (#756) resolve ONLY through the dedicated matcher —
|
|
1443
1713
|
// never the fuzzy/qualified fallthrough below (a wrong callback edge is
|
|
@@ -1445,6 +1715,57 @@ function matchReference(ref, context) {
|
|
|
1445
1715
|
if (ref.referenceKind === 'function_ref') {
|
|
1446
1716
|
return matchFunctionRef(ref, context);
|
|
1447
1717
|
}
|
|
1718
|
+
// ArkTS chained UI attributes — emitted with a leading dot (`.titleStyle`,
|
|
1719
|
+
// `.width`) by the extractor — resolve ONLY to decorator-marked attribute
|
|
1720
|
+
// helpers: `@Extend`/`@Styles`/`@AnimatableExtend` functions (and global
|
|
1721
|
+
// `@Builder`s used attribute-position). Framework attributes (`.width`,
|
|
1722
|
+
// `.fontSize` — on nearly every UI line) match no such helper and stay
|
|
1723
|
+
// unresolved, NEVER falling through to bare-name matching: on a samples
|
|
1724
|
+
// monorepo that fallthrough manufactured 36k wrong edges, giving single
|
|
1725
|
+
// same-named properties thousands of false callers. Ambiguity rule matches
|
|
1726
|
+
// the rest of the file: several same-named helpers → prefer the call-site
|
|
1727
|
+
// file, still ambiguous → drop the ref rather than guess.
|
|
1728
|
+
if (ref.language === 'arkts' && ref.referenceName.startsWith('.')) {
|
|
1729
|
+
const base = ref.referenceName.slice(1);
|
|
1730
|
+
const candidates = context
|
|
1731
|
+
.getNodesByName(base)
|
|
1732
|
+
.filter((n) => n.language === 'arkts' &&
|
|
1733
|
+
n.kind === 'function' &&
|
|
1734
|
+
(n.decorators ?? []).some((d) => ARKUI_ATTRIBUTE_DECORATORS.has(d)));
|
|
1735
|
+
const chosen = candidates.length > 1 ? preferCallSiteFile(candidates, ref.filePath) : candidates;
|
|
1736
|
+
if (chosen.length !== 1)
|
|
1737
|
+
return null;
|
|
1738
|
+
return {
|
|
1739
|
+
original: ref,
|
|
1740
|
+
targetNodeId: chosen[0].id,
|
|
1741
|
+
confidence: 0.85,
|
|
1742
|
+
resolvedBy: 'exact-match',
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
// Erlang `-behaviour(m)` refs target a MODULE. Letting them fall through to
|
|
1746
|
+
// bare-name matching grabs any same-named symbol — on emqx,
|
|
1747
|
+
// `-behaviour(supervisor)` resolved to a `-define(supervisor, …)` macro
|
|
1748
|
+
// constant in an unrelated app. Resolve only to the behaviour module's
|
|
1749
|
+
// namespace; an out-of-repo behaviour (OTP's gen_server/supervisor) stays
|
|
1750
|
+
// unresolved rather than guessed. The same module-only rule applies to every
|
|
1751
|
+
// ref an `.app`/`.app.src` resource file emits — its `{mod, …}` callback and
|
|
1752
|
+
// `{applications, …}` dependency names can only mean modules, and on emqx
|
|
1753
|
+
// the `ssl` OTP app otherwise resolved to a test helper FUNCTION named ssl.
|
|
1754
|
+
if (ref.language === 'erlang' &&
|
|
1755
|
+
(ref.referenceKind === 'implements' || /\.app(?:\.src)?$/i.test(ref.filePath))) {
|
|
1756
|
+
const modules = context
|
|
1757
|
+
.getNodesByName(ref.referenceName)
|
|
1758
|
+
.filter((n) => n.language === 'erlang' && n.kind === 'namespace');
|
|
1759
|
+
const chosen = preferCallSiteFile(modules, ref.filePath)[0];
|
|
1760
|
+
if (!chosen)
|
|
1761
|
+
return null;
|
|
1762
|
+
return {
|
|
1763
|
+
original: ref,
|
|
1764
|
+
targetNodeId: chosen.id,
|
|
1765
|
+
confidence: 0.9,
|
|
1766
|
+
resolvedBy: 'exact-match',
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1448
1769
|
// Try strategies in order of confidence
|
|
1449
1770
|
let result;
|
|
1450
1771
|
// 0. File path match (e.g., "snippets/drawer-menu.liquid" → file node)
|