homegraph 1.6.3 → 1.6.4
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/CHANGELOG.md +15 -0
- package/dist/bin/homegraph.js +15 -9
- package/dist/db/index.d.ts +21 -0
- package/dist/db/index.js +56 -0
- package/dist/directory.d.ts +17 -0
- package/dist/directory.js +40 -0
- package/dist/extraction/grammars.js +7 -0
- package/dist/extraction/index.d.ts +5 -0
- package/dist/extraction/index.js +162 -116
- package/dist/extraction/jsdoc-type-tags.d.ts +19 -0
- package/dist/extraction/jsdoc-type-tags.js +115 -0
- package/dist/extraction/languages/arkts.js +59 -23
- package/dist/extraction/store-flush-guard.d.ts +11 -0
- package/dist/extraction/store-flush-guard.js +19 -0
- package/dist/extraction/store-worker.d.ts +3 -2
- package/dist/extraction/store-worker.js +3 -2
- package/dist/extraction/tree-sitter.d.ts +8 -1
- package/dist/extraction/tree-sitter.js +52 -11
- package/dist/index.js +31 -1
- package/dist/resolution/callback-synthesizer.d.ts +12 -0
- package/dist/resolution/callback-synthesizer.js +5 -8
- package/dist/resolution/name-matcher.d.ts +5 -0
- package/dist/resolution/name-matcher.js +60 -1
- package/package.json +3 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec 0066 — parse JSDoc `@returns` / `@param` type tags as a fallback when
|
|
3
|
+
* the signature has no usable return/param type name. Docstring text still
|
|
4
|
+
* lives on `nodes.docstring` (Spec 0020); this only extracts type *names*.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Unwrap Promise/Array/… and strip arrays/generics to a bare type identifier.
|
|
8
|
+
* Returns undefined for empty / primitive / void-like results.
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractBaseTypeName(raw: string | undefined | null): string | undefined;
|
|
11
|
+
/** `@returns {Type}` or `@return Type` → base type name. */
|
|
12
|
+
export declare function parseJsdocReturns(docstring: string | undefined | null): string | undefined;
|
|
13
|
+
export interface JsdocParamType {
|
|
14
|
+
paramName: string;
|
|
15
|
+
typeName: string;
|
|
16
|
+
}
|
|
17
|
+
/** `@param {Type} name` entries with a resolvable type name. */
|
|
18
|
+
export declare function parseJsdocParams(docstring: string | undefined | null): JsdocParamType[];
|
|
19
|
+
//# sourceMappingURL=jsdoc-type-tags.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Spec 0066 — parse JSDoc `@returns` / `@param` type tags as a fallback when
|
|
4
|
+
* the signature has no usable return/param type name. Docstring text still
|
|
5
|
+
* lives on `nodes.docstring` (Spec 0020); this only extracts type *names*.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.extractBaseTypeName = extractBaseTypeName;
|
|
9
|
+
exports.parseJsdocReturns = parseJsdocReturns;
|
|
10
|
+
exports.parseJsdocParams = parseJsdocParams;
|
|
11
|
+
const PRIMITIVE_OR_VOID = new Set([
|
|
12
|
+
'void',
|
|
13
|
+
'undefined',
|
|
14
|
+
'null',
|
|
15
|
+
'never',
|
|
16
|
+
'any',
|
|
17
|
+
'unknown',
|
|
18
|
+
'object',
|
|
19
|
+
'Object',
|
|
20
|
+
'string',
|
|
21
|
+
'String',
|
|
22
|
+
'number',
|
|
23
|
+
'Number',
|
|
24
|
+
'boolean',
|
|
25
|
+
'Boolean',
|
|
26
|
+
'bigint',
|
|
27
|
+
'symbol',
|
|
28
|
+
'Symbol',
|
|
29
|
+
'this',
|
|
30
|
+
'self',
|
|
31
|
+
'static',
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Unwrap Promise/Array/… and strip arrays/generics to a bare type identifier.
|
|
35
|
+
* Returns undefined for empty / primitive / void-like results.
|
|
36
|
+
*/
|
|
37
|
+
function extractBaseTypeName(raw) {
|
|
38
|
+
if (!raw)
|
|
39
|
+
return undefined;
|
|
40
|
+
let t = raw.trim();
|
|
41
|
+
if (!t)
|
|
42
|
+
return undefined;
|
|
43
|
+
// Drop leading `readonly` / `export` noise occasionally glued into tags.
|
|
44
|
+
t = t.replace(/^(?:readonly|export|declare)\s+/i, '').trim();
|
|
45
|
+
// Take the first union arm: `Foo | null` → `Foo`.
|
|
46
|
+
if (t.includes('|')) {
|
|
47
|
+
t = t.split('|')[0].trim();
|
|
48
|
+
}
|
|
49
|
+
// Function type → skip (no single type node).
|
|
50
|
+
if (t.includes('=>'))
|
|
51
|
+
return undefined;
|
|
52
|
+
if (t.endsWith('[]')) {
|
|
53
|
+
t = t.slice(0, -2).trim();
|
|
54
|
+
}
|
|
55
|
+
const wrapper = t.match(/^(?:Promise|Array|ReadonlyArray|Callback|Set|Iterable|Partial|Required|Readonly)\s*<\s*(.+)\s*>$/i);
|
|
56
|
+
if (wrapper?.[1]) {
|
|
57
|
+
t = wrapper[1].trim();
|
|
58
|
+
// Nested commas in Map/Record — take last type arg when present.
|
|
59
|
+
if (t.includes(',')) {
|
|
60
|
+
const mapLike = t.match(/^(?:Map|Record)\s*<[^,]+,\s*(.+)>$/i);
|
|
61
|
+
if (mapLike?.[1])
|
|
62
|
+
t = mapLike[1].trim();
|
|
63
|
+
else
|
|
64
|
+
t = t.split(',').pop().trim();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const mapMatch = t.match(/^(?:Map|Record)\s*<[^,]+,\s*(.+)>$/i);
|
|
68
|
+
if (mapMatch?.[1])
|
|
69
|
+
t = mapMatch[1].trim();
|
|
70
|
+
// Strip remaining generics: `Foo<T>` → `Foo`
|
|
71
|
+
t = t.replace(/<[^>]*>/g, '').replace(/\[\]/g, '').trim();
|
|
72
|
+
if (!t)
|
|
73
|
+
return undefined;
|
|
74
|
+
// Qualified → trailing simple name (`ns.Bar` → `Bar`)
|
|
75
|
+
const simple = t.split('.').filter(Boolean).pop() ?? t;
|
|
76
|
+
if (!simple || PRIMITIVE_OR_VOID.has(simple))
|
|
77
|
+
return undefined;
|
|
78
|
+
// Must look like an identifier (PascalCase preferred but allow any word).
|
|
79
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(simple))
|
|
80
|
+
return undefined;
|
|
81
|
+
return simple;
|
|
82
|
+
}
|
|
83
|
+
/** `@returns {Type}` or `@return Type` → base type name. */
|
|
84
|
+
function parseJsdocReturns(docstring) {
|
|
85
|
+
if (!docstring)
|
|
86
|
+
return undefined;
|
|
87
|
+
const re = /@returns?\s+(?:\{([^}]+)\}|([A-Za-z_][\w.<>[\],\s|]*))/gi;
|
|
88
|
+
let m;
|
|
89
|
+
while ((m = re.exec(docstring)) !== null) {
|
|
90
|
+
const raw = (m[1] ?? m[2] ?? '').trim();
|
|
91
|
+
const base = extractBaseTypeName(raw);
|
|
92
|
+
if (base)
|
|
93
|
+
return base;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
/** `@param {Type} name` entries with a resolvable type name. */
|
|
98
|
+
function parseJsdocParams(docstring) {
|
|
99
|
+
if (!docstring)
|
|
100
|
+
return [];
|
|
101
|
+
const out = [];
|
|
102
|
+
const re = /@param(?:eter)?\s+(?:\{([^}]+)\}\s+)?\[?(\w+)\]?/gi;
|
|
103
|
+
let m;
|
|
104
|
+
while ((m = re.exec(docstring)) !== null) {
|
|
105
|
+
const typeRaw = m[1]?.trim();
|
|
106
|
+
const paramName = m[2];
|
|
107
|
+
if (!typeRaw || !paramName)
|
|
108
|
+
continue;
|
|
109
|
+
const typeName = extractBaseTypeName(typeRaw);
|
|
110
|
+
if (typeName)
|
|
111
|
+
out.push({ paramName, typeName });
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=jsdoc-type-tags.js.map
|
|
@@ -141,6 +141,7 @@ const liveness_watchdog_1 = require("../../mcp/liveness-watchdog");
|
|
|
141
141
|
const migrate_semantics_1 = require("../../arkui/migrate-semantics");
|
|
142
142
|
const migrate_passage_1 = require("../../arkui/migrate-passage");
|
|
143
143
|
const tree_sitter_helpers_1 = require("../tree-sitter-helpers");
|
|
144
|
+
const jsdoc_type_tags_1 = require("../jsdoc-type-tags");
|
|
144
145
|
const wasm_runtime_flags_1 = require("../wasm-runtime-flags");
|
|
145
146
|
const ignore_1 = __importDefault(require("ignore"));
|
|
146
147
|
/** Primitive / void return types — not stored in nodes.return_type (inference-only column). */
|
|
@@ -3995,29 +3996,18 @@ class ArkTSAdapter {
|
|
|
3995
3996
|
this.crossFileEdges.push(edge);
|
|
3996
3997
|
}
|
|
3997
3998
|
}
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
// fall through
|
|
4011
|
-
}
|
|
4012
|
-
if (shouldSkipArkMethod(method) && !isAnonymousArkMethod(method))
|
|
4013
|
-
return null;
|
|
4014
|
-
const displayName = arkMethodDisplayName(method);
|
|
4015
|
-
if (!displayName)
|
|
4016
|
-
return null;
|
|
4017
|
-
const cls = method.getDeclaringArkClass();
|
|
4018
|
-
const kind = cls.isDefaultArkClass() ? 'function' : 'method';
|
|
4019
|
-
this.indexMethod(relativePath, languageForArkAnalyzerPath(relativePath), result, method, parentId, kind, displayName);
|
|
4020
|
-
return this.methodToId.get(method) ?? null;
|
|
3999
|
+
/**
|
|
4000
|
+
* Lazily index a method for ViewTree (and any caller that used to pass the
|
|
4001
|
+
* ViewTree owner path). Attribution always follows the method's declaring
|
|
4002
|
+
* ArkFile / class — same as {@link resolveMethodNodeId} — so nested ViewTree
|
|
4003
|
+
* walks cannot pin `%AM*` handlers onto the importer (Spec 0064).
|
|
4004
|
+
*
|
|
4005
|
+
* `relativePath` / `result` / `parentId` from the caller are ignored for the
|
|
4006
|
+
* node row (kept on the signature so ViewTree call sites stay unchanged);
|
|
4007
|
+
* ViewTree *edges* still use the importer via {@link addViewTreeEdge}.
|
|
4008
|
+
*/
|
|
4009
|
+
ensureMethodNode(method, _relativePath, _result, _parentId) {
|
|
4010
|
+
return this.resolveMethodNodeId(method);
|
|
4021
4011
|
}
|
|
4022
4012
|
indexMethod(relativePath, language, result, method, parentId, forcedKind, forcedDisplayName) {
|
|
4023
4013
|
const displayName = forcedDisplayName ?? resolveMethodDisplayName(method);
|
|
@@ -4057,6 +4047,52 @@ class ArkTSAdapter {
|
|
|
4057
4047
|
result.edges.push(arkEdge(parentId, methodNode.id, 'contains'));
|
|
4058
4048
|
this.indexDecorators(result, methodNode.id, method, relativePath, language, line);
|
|
4059
4049
|
this.indexNativeNapiCalls(method, methodNode.id, relativePath, language, result);
|
|
4050
|
+
// Spec 0066: return/param types as resolvable `returns` / `type_of` refs.
|
|
4051
|
+
this.emitMethodTypeEdges(method, methodNode.id, relativePath, language, line, returnType, methodNode.docstring, result);
|
|
4052
|
+
}
|
|
4053
|
+
/**
|
|
4054
|
+
* Spec 0066 — push unresolved `returns` / `type_of` from signature types,
|
|
4055
|
+
* falling back to JSDoc `@returns` / `@param` when the signature lacks them.
|
|
4056
|
+
*/
|
|
4057
|
+
emitMethodTypeEdges(method, fromNodeId, relativePath, language, line, returnType, docstring, result) {
|
|
4058
|
+
const push = (referenceName, referenceKind) => {
|
|
4059
|
+
result.unresolvedReferences.push({
|
|
4060
|
+
fromNodeId,
|
|
4061
|
+
referenceName,
|
|
4062
|
+
referenceKind,
|
|
4063
|
+
line,
|
|
4064
|
+
column: 0,
|
|
4065
|
+
filePath: relativePath,
|
|
4066
|
+
language,
|
|
4067
|
+
});
|
|
4068
|
+
};
|
|
4069
|
+
if (returnType) {
|
|
4070
|
+
push(returnType, 'returns');
|
|
4071
|
+
}
|
|
4072
|
+
else {
|
|
4073
|
+
const fromDoc = (0, jsdoc_type_tags_1.parseJsdocReturns)(docstring);
|
|
4074
|
+
if (fromDoc)
|
|
4075
|
+
push(fromDoc, 'returns');
|
|
4076
|
+
}
|
|
4077
|
+
let emittedParam = false;
|
|
4078
|
+
try {
|
|
4079
|
+
for (const p of method.getParameters()) {
|
|
4080
|
+
const raw = p.getType()?.toString();
|
|
4081
|
+
const base = normalizeArktsReturnType(raw);
|
|
4082
|
+
if (base) {
|
|
4083
|
+
push(base, 'type_of');
|
|
4084
|
+
emittedParam = true;
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
catch {
|
|
4089
|
+
// parameters unavailable
|
|
4090
|
+
}
|
|
4091
|
+
if (!emittedParam) {
|
|
4092
|
+
for (const p of (0, jsdoc_type_tags_1.parseJsdocParams)(docstring)) {
|
|
4093
|
+
push(p.typeName, 'type_of');
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4060
4096
|
}
|
|
4061
4097
|
indexNativeNapiCalls(method, fromNodeId, relativePath, language, result) {
|
|
4062
4098
|
const code = method.getCode();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store-phase flush / backpressure guards (Spec 0065).
|
|
3
|
+
*
|
|
4
|
+
* When `flushOrdered` records a store failure in `flushError` but returns a
|
|
5
|
+
* resolved promise, the feed backpressure `while` would otherwise spin on
|
|
6
|
+
* `await Promise.resolve()` forever (100% CPU hang). Call this at the top of
|
|
7
|
+
* that loop (and anywhere else that waits on flush progress).
|
|
8
|
+
*/
|
|
9
|
+
/** Throw if the ordered store flush already failed or the run was aborted. */
|
|
10
|
+
export declare function throwIfStoreFlushFailed(aborted: boolean, flushError: unknown): void;
|
|
11
|
+
//# sourceMappingURL=store-flush-guard.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Store-phase flush / backpressure guards (Spec 0065).
|
|
4
|
+
*
|
|
5
|
+
* When `flushOrdered` records a store failure in `flushError` but returns a
|
|
6
|
+
* resolved promise, the feed backpressure `while` would otherwise spin on
|
|
7
|
+
* `await Promise.resolve()` forever (100% CPU hang). Call this at the top of
|
|
8
|
+
* that loop (and anywhere else that waits on flush progress).
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.throwIfStoreFlushFailed = throwIfStoreFlushFailed;
|
|
12
|
+
/** Throw if the ordered store flush already failed or the run was aborted. */
|
|
13
|
+
function throwIfStoreFlushFailed(aborted, flushError) {
|
|
14
|
+
if (flushError)
|
|
15
|
+
throw flushError;
|
|
16
|
+
if (aborted)
|
|
17
|
+
throw new Error('Aborted');
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=store-flush-guard.js.map
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* connection: the orchestrator posts one message per file (in file order) and
|
|
7
7
|
* the worker applies them in arrival order, which preserves the #1015
|
|
8
8
|
* insertion-order determinism exactly as if the main thread had run the same
|
|
9
|
-
* calls.
|
|
10
|
-
*
|
|
9
|
+
* calls. Spec 0065: the main connection is closed for the worker's lifetime
|
|
10
|
+
* (`exclusiveHandoff`) — an idle open handle on MEMORY/non-WAL backends was
|
|
11
|
+
* enough to surface SQLITE_BUSY during ArkTS→store handoff.
|
|
11
12
|
*
|
|
12
13
|
* Protocol (main → worker):
|
|
13
14
|
* {type:'open', dbPath, fastInit} → open connection, reply {type:'ready'}
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* connection: the orchestrator posts one message per file (in file order) and
|
|
8
8
|
* the worker applies them in arrival order, which preserves the #1015
|
|
9
9
|
* insertion-order determinism exactly as if the main thread had run the same
|
|
10
|
-
* calls.
|
|
11
|
-
*
|
|
10
|
+
* calls. Spec 0065: the main connection is closed for the worker's lifetime
|
|
11
|
+
* (`exclusiveHandoff`) — an idle open handle on MEMORY/non-WAL backends was
|
|
12
|
+
* enough to surface SQLITE_BUSY during ArkTS→store handoff.
|
|
12
13
|
*
|
|
13
14
|
* Protocol (main → worker):
|
|
14
15
|
* {type:'open', dbPath, fastInit} → open connection, reply {type:'ready'}
|
|
@@ -595,9 +595,15 @@ export declare class TreeSitterExtractor {
|
|
|
595
595
|
private readonly BUILTIN_TYPES;
|
|
596
596
|
/**
|
|
597
597
|
* Extract type references from type annotations on a function/method/field node.
|
|
598
|
-
*
|
|
598
|
+
* Spec 0066: parameter / field types → `type_of`; return types → `returns`.
|
|
599
|
+
* Dart / Scala mixed signature walks stay `references` (out of this Spec).
|
|
599
600
|
*/
|
|
600
601
|
private extractTypeAnnotations;
|
|
602
|
+
/**
|
|
603
|
+
* Spec 0066: when the AST omitted return/param types, emit unresolved
|
|
604
|
+
* `returns` / `type_of` refs from JSDoc `@returns` / `@param {Type}`.
|
|
605
|
+
*/
|
|
606
|
+
private emitJsdocTypeFallbacks;
|
|
601
607
|
/**
|
|
602
608
|
* Extract C# type references from a node that owns a type position —
|
|
603
609
|
* a method/constructor declaration, a property declaration, or a
|
|
@@ -647,6 +653,7 @@ export declare class TreeSitterExtractor {
|
|
|
647
653
|
/**
|
|
648
654
|
* Recursively walk a subtree and extract all type_identifier references.
|
|
649
655
|
* Handles unions, intersections, generics, arrays, etc.
|
|
656
|
+
* Spec 0066: callers pass `returns` / `type_of` for signature positions.
|
|
650
657
|
*/
|
|
651
658
|
private extractTypeRefsFromSubtree;
|
|
652
659
|
/**
|
|
@@ -47,6 +47,7 @@ const function_ref_1 = require("./function-ref");
|
|
|
47
47
|
const generated_detection_1 = require("./generated-detection");
|
|
48
48
|
const languages_1 = require("./languages");
|
|
49
49
|
const c_cpp_1 = require("./languages/c-cpp");
|
|
50
|
+
const jsdoc_type_tags_1 = require("./jsdoc-type-tags");
|
|
50
51
|
const liquid_extractor_1 = require("./liquid-extractor");
|
|
51
52
|
const razor_extractor_1 = require("./razor-extractor");
|
|
52
53
|
const svelte_extractor_1 = require("./svelte-extractor");
|
|
@@ -1511,6 +1512,8 @@ class TreeSitterExtractor {
|
|
|
1511
1512
|
return;
|
|
1512
1513
|
// Extract type annotations (parameter types and return type)
|
|
1513
1514
|
this.extractTypeAnnotations(node, funcNode.id);
|
|
1515
|
+
// Spec 0066: JSDoc @returns/@param when the signature omitted types.
|
|
1516
|
+
this.emitJsdocTypeFallbacks(funcNode.id, docstring);
|
|
1514
1517
|
// Extract decorators applied to the function (rare in JS/TS but
|
|
1515
1518
|
// present in Python `@decorator def f():` and Java/Kotlin
|
|
1516
1519
|
// annotations on free functions).
|
|
@@ -1712,6 +1715,8 @@ class TreeSitterExtractor {
|
|
|
1712
1715
|
}
|
|
1713
1716
|
// Extract type annotations (parameter types and return type)
|
|
1714
1717
|
this.extractTypeAnnotations(node, methodNode.id);
|
|
1718
|
+
// Spec 0066: JSDoc @returns/@param when the signature omitted types.
|
|
1719
|
+
this.emitJsdocTypeFallbacks(methodNode.id, docstring);
|
|
1715
1720
|
// Extract decorators (`@Get('/list') list() {}`).
|
|
1716
1721
|
this.extractDecoratorsFor(node, methodNode.id);
|
|
1717
1722
|
// Push to stack and visit body
|
|
@@ -5636,7 +5641,8 @@ class TreeSitterExtractor {
|
|
|
5636
5641
|
]);
|
|
5637
5642
|
/**
|
|
5638
5643
|
* Extract type references from type annotations on a function/method/field node.
|
|
5639
|
-
*
|
|
5644
|
+
* Spec 0066: parameter / field types → `type_of`; return types → `returns`.
|
|
5645
|
+
* Dart / Scala mixed signature walks stay `references` (out of this Spec).
|
|
5640
5646
|
*/
|
|
5641
5647
|
extractTypeAnnotations(node, nodeId) {
|
|
5642
5648
|
if (!this.extractor)
|
|
@@ -5668,6 +5674,7 @@ class TreeSitterExtractor {
|
|
|
5668
5674
|
// `type_identifier` child, not a `type` field — so getChildByField below
|
|
5669
5675
|
// finds neither. Walk the inner signature: param names / the method name are
|
|
5670
5676
|
// `identifier` (not `type_identifier`), so only types surface.
|
|
5677
|
+
// Spec 0066: leave as `references` (mixed return+param walk).
|
|
5671
5678
|
if (this.language === 'dart') {
|
|
5672
5679
|
let sig = node;
|
|
5673
5680
|
if (node.type === 'method_signature') {
|
|
@@ -5677,7 +5684,7 @@ class TreeSitterExtractor {
|
|
|
5677
5684
|
c.type === 'constructor_signature' ||
|
|
5678
5685
|
c.type === 'factory_constructor_signature') ?? node;
|
|
5679
5686
|
}
|
|
5680
|
-
this.extractTypeRefsFromSubtree(sig, nodeId);
|
|
5687
|
+
this.extractTypeRefsFromSubtree(sig, nodeId, 'references');
|
|
5681
5688
|
return;
|
|
5682
5689
|
}
|
|
5683
5690
|
// Extract parameter type annotations. Scala curries — `def f(a)(implicit
|
|
@@ -5687,19 +5694,19 @@ class TreeSitterExtractor {
|
|
|
5687
5694
|
if (this.language === 'scala') {
|
|
5688
5695
|
for (const pc of node.namedChildren) {
|
|
5689
5696
|
if (pc.type === 'parameters')
|
|
5690
|
-
this.extractTypeRefsFromSubtree(pc, nodeId);
|
|
5697
|
+
this.extractTypeRefsFromSubtree(pc, nodeId, 'type_of');
|
|
5691
5698
|
}
|
|
5692
5699
|
}
|
|
5693
5700
|
else {
|
|
5694
5701
|
const params = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.paramsField || 'parameters');
|
|
5695
5702
|
if (params) {
|
|
5696
|
-
this.extractTypeRefsFromSubtree(params, nodeId);
|
|
5703
|
+
this.extractTypeRefsFromSubtree(params, nodeId, 'type_of');
|
|
5697
5704
|
}
|
|
5698
5705
|
}
|
|
5699
5706
|
// Extract return type annotation
|
|
5700
5707
|
const returnType = (0, tree_sitter_helpers_1.getChildByField)(node, this.extractor.returnField || 'return_type');
|
|
5701
5708
|
if (returnType) {
|
|
5702
|
-
this.extractTypeRefsFromSubtree(returnType, nodeId);
|
|
5709
|
+
this.extractTypeRefsFromSubtree(returnType, nodeId, 'returns');
|
|
5703
5710
|
}
|
|
5704
5711
|
// Scala context bounds / type-parameter bounds: `def f[A: Monoid]`,
|
|
5705
5712
|
// `[F[_]: Monad]`, `[A <: Foo]` carry the bound type inside `type_parameters`.
|
|
@@ -5711,13 +5718,46 @@ class TreeSitterExtractor {
|
|
|
5711
5718
|
if (this.language === 'scala') {
|
|
5712
5719
|
const typeParams = node.namedChildren.find((c) => c.type === 'type_parameters');
|
|
5713
5720
|
if (typeParams) {
|
|
5714
|
-
this.extractTypeRefsFromSubtree(typeParams, nodeId);
|
|
5721
|
+
this.extractTypeRefsFromSubtree(typeParams, nodeId, 'type_of');
|
|
5715
5722
|
}
|
|
5716
5723
|
}
|
|
5717
5724
|
// Extract direct type annotation (for class fields like `model: ITextModel`)
|
|
5718
5725
|
const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
|
|
5719
5726
|
if (typeAnnotation) {
|
|
5720
|
-
this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
|
|
5727
|
+
this.extractTypeRefsFromSubtree(typeAnnotation, nodeId, 'type_of');
|
|
5728
|
+
}
|
|
5729
|
+
}
|
|
5730
|
+
/**
|
|
5731
|
+
* Spec 0066: when the AST omitted return/param types, emit unresolved
|
|
5732
|
+
* `returns` / `type_of` refs from JSDoc `@returns` / `@param {Type}`.
|
|
5733
|
+
*/
|
|
5734
|
+
emitJsdocTypeFallbacks(fromNodeId, docstring) {
|
|
5735
|
+
if (!docstring)
|
|
5736
|
+
return;
|
|
5737
|
+
const hasReturns = this.unresolvedReferences.some((r) => r.fromNodeId === fromNodeId && r.referenceKind === 'returns');
|
|
5738
|
+
if (!hasReturns) {
|
|
5739
|
+
const ret = (0, jsdoc_type_tags_1.parseJsdocReturns)(docstring);
|
|
5740
|
+
if (ret) {
|
|
5741
|
+
this.unresolvedReferences.push({
|
|
5742
|
+
fromNodeId,
|
|
5743
|
+
referenceName: ret,
|
|
5744
|
+
referenceKind: 'returns',
|
|
5745
|
+
line: 1,
|
|
5746
|
+
column: 0,
|
|
5747
|
+
});
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
5750
|
+
const hasTypeOf = this.unresolvedReferences.some((r) => r.fromNodeId === fromNodeId && r.referenceKind === 'type_of');
|
|
5751
|
+
if (!hasTypeOf) {
|
|
5752
|
+
for (const p of (0, jsdoc_type_tags_1.parseJsdocParams)(docstring)) {
|
|
5753
|
+
this.unresolvedReferences.push({
|
|
5754
|
+
fromNodeId,
|
|
5755
|
+
referenceName: p.typeName,
|
|
5756
|
+
referenceKind: 'type_of',
|
|
5757
|
+
line: 1,
|
|
5758
|
+
column: 0,
|
|
5759
|
+
});
|
|
5760
|
+
}
|
|
5721
5761
|
}
|
|
5722
5762
|
}
|
|
5723
5763
|
/**
|
|
@@ -5923,21 +5963,22 @@ class TreeSitterExtractor {
|
|
|
5923
5963
|
// Find type_annotation child (covers TS `: Type`, Rust `: Type`, etc.)
|
|
5924
5964
|
const typeAnnotation = node.namedChildren.find((c) => c.type === 'type_annotation');
|
|
5925
5965
|
if (typeAnnotation) {
|
|
5926
|
-
this.extractTypeRefsFromSubtree(typeAnnotation, nodeId);
|
|
5966
|
+
this.extractTypeRefsFromSubtree(typeAnnotation, nodeId, 'type_of');
|
|
5927
5967
|
}
|
|
5928
5968
|
}
|
|
5929
5969
|
/**
|
|
5930
5970
|
* Recursively walk a subtree and extract all type_identifier references.
|
|
5931
5971
|
* Handles unions, intersections, generics, arrays, etc.
|
|
5972
|
+
* Spec 0066: callers pass `returns` / `type_of` for signature positions.
|
|
5932
5973
|
*/
|
|
5933
|
-
extractTypeRefsFromSubtree(node, fromNodeId) {
|
|
5974
|
+
extractTypeRefsFromSubtree(node, fromNodeId, referenceKind = 'references') {
|
|
5934
5975
|
if (node.type === 'type_identifier') {
|
|
5935
5976
|
const typeName = (0, tree_sitter_helpers_1.getNodeText)(node, this.source);
|
|
5936
5977
|
if (typeName && !this.BUILTIN_TYPES.has(typeName)) {
|
|
5937
5978
|
this.unresolvedReferences.push({
|
|
5938
5979
|
fromNodeId,
|
|
5939
5980
|
referenceName: typeName,
|
|
5940
|
-
referenceKind
|
|
5981
|
+
referenceKind,
|
|
5941
5982
|
line: node.startPosition.row + 1,
|
|
5942
5983
|
column: node.startPosition.column,
|
|
5943
5984
|
});
|
|
@@ -5948,7 +5989,7 @@ class TreeSitterExtractor {
|
|
|
5948
5989
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
5949
5990
|
const child = node.namedChild(i);
|
|
5950
5991
|
if (child) {
|
|
5951
|
-
this.extractTypeRefsFromSubtree(child, fromNodeId);
|
|
5992
|
+
this.extractTypeRefsFromSubtree(child, fromNodeId, referenceKind);
|
|
5952
5993
|
}
|
|
5953
5994
|
}
|
|
5954
5995
|
}
|
package/dist/index.js
CHANGED
|
@@ -490,7 +490,37 @@ class HomeGraph {
|
|
|
490
490
|
this.db.beginBulkParseLoad();
|
|
491
491
|
let result;
|
|
492
492
|
try {
|
|
493
|
-
result = await this.orchestrator.indexAll(options.onProgress, options.signal, options.verbose, walValve ? () => walValve.backpressure() : undefined, freshDb
|
|
493
|
+
result = await this.orchestrator.indexAll(options.onProgress, options.signal, options.verbose, walValve ? () => walValve.backpressure() : undefined, freshDb
|
|
494
|
+
? {
|
|
495
|
+
dbPath: this.db.getPath(),
|
|
496
|
+
fastInit,
|
|
497
|
+
// Spec 0065: exclusive store-worker handoff (close main → worker → reopen).
|
|
498
|
+
exclusiveHandoff: {
|
|
499
|
+
release: () => {
|
|
500
|
+
this.db.closeForExclusiveWriter();
|
|
501
|
+
},
|
|
502
|
+
reclaim: () => {
|
|
503
|
+
this.db.reopenAfterExclusiveWriter({ fastInit });
|
|
504
|
+
this.queries.rebind(this.db.getDb());
|
|
505
|
+
(0, context_1.bindExtractionContext)(this.projectRoot, this.queries);
|
|
506
|
+
// ATTACH is per-connection. ohosApiDbPath survives rebind and
|
|
507
|
+
// would make attachOhosApiDb early-return while the schema
|
|
508
|
+
// is gone → "no such table: ohos_api.nodes". Clear then reattach.
|
|
509
|
+
const priorOhos = this.queries.getOhosApiDbPath();
|
|
510
|
+
this.queries.attachOhosApiDb(null);
|
|
511
|
+
if (priorOhos) {
|
|
512
|
+
this.queries.attachOhosApiDb(priorOhos);
|
|
513
|
+
}
|
|
514
|
+
else if ((0, graph_sources_1.graphSourceFlags)(this.graphSources).sdk) {
|
|
515
|
+
(0, arkts_1.restoreOhosApiDbAttach)(this.queries);
|
|
516
|
+
if (!this.queries.getOhosApiDbPath()) {
|
|
517
|
+
(0, arkts_1.attachExistingOhosApiDbForProject)(this.projectRoot, this.queries);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
}
|
|
523
|
+
: null);
|
|
494
524
|
}
|
|
495
525
|
finally {
|
|
496
526
|
if (freshDb) {
|
|
@@ -25,6 +25,18 @@ import type { Edge } from '../types';
|
|
|
25
25
|
import type { QueryBuilder } from '../db/queries';
|
|
26
26
|
import type { ResolutionContext } from './types';
|
|
27
27
|
import { type MaybeYield } from './cooperative-yield';
|
|
28
|
+
/**
|
|
29
|
+
* Spec 0045 A — ArkUI parent build/@Builder → child @Component/@Builder.
|
|
30
|
+
* Analog of react jsx-child: only static PascalCase call names that resolve to
|
|
31
|
+
* a unique in-repo component/builder; ambiguous names are dropped.
|
|
32
|
+
*
|
|
33
|
+
* Scan the enclosing @Component/@Builder struct (or Builder function) body —
|
|
34
|
+
* not only the `build()` method span — because ArkUI trailing lambdas
|
|
35
|
+
* (`Column() { Child() }`) are often attributed with tight method ranges that
|
|
36
|
+
* omit nested call sites.
|
|
37
|
+
*/
|
|
38
|
+
/** @internal exported for PERF kind-scan regression tests (resolveChild name-miss path). */
|
|
39
|
+
export declare function arkuiChildComponentEdges(ctx: ResolutionContext, onYield: MaybeYield): Promise<Edge[]>;
|
|
28
40
|
/** `has(...)` shape passed to pass gates — true when the project contains any of the languages. */
|
|
29
41
|
type HasLang = (...ls: string[]) => boolean;
|
|
30
42
|
/**
|
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.SYNTH_PROGRESS_STEPS = exports.SYNTH_PASSES = void 0;
|
|
37
|
+
exports.arkuiChildComponentEdges = arkuiChildComponentEdges;
|
|
37
38
|
exports.synthesizeCallbackEdges = synthesizeCallbackEdges;
|
|
38
39
|
const generated_detection_1 = require("../extraction/generated-detection");
|
|
39
40
|
const strip_comments_1 = require("./strip-comments");
|
|
@@ -1018,6 +1019,7 @@ function isArkuiBuildParent(n) {
|
|
|
1018
1019
|
* (`Column() { Child() }`) are often attributed with tight method ranges that
|
|
1019
1020
|
* omit nested call sites.
|
|
1020
1021
|
*/
|
|
1022
|
+
/** @internal exported for PERF kind-scan regression tests (resolveChild name-miss path). */
|
|
1021
1023
|
async function arkuiChildComponentEdges(ctx, onYield) {
|
|
1022
1024
|
const edges = [];
|
|
1023
1025
|
const seen = new Set();
|
|
@@ -1033,15 +1035,10 @@ async function arkuiChildComponentEdges(ctx, onYield) {
|
|
|
1033
1035
|
return '';
|
|
1034
1036
|
};
|
|
1035
1037
|
const resolveChild = (name, fromFile, parentId) => {
|
|
1038
|
+
// Name index only — miss returns null. Framework PascalCase calls
|
|
1039
|
+
// (Column/Button/Text/…) dominate misses and must not full-scan kinds
|
|
1040
|
+
// (Spec 0063). Same drop policy as ambiguous multi-match below.
|
|
1036
1041
|
let candidates = ctx.getNodesByName(name).filter(isArkuiChildTarget);
|
|
1037
|
-
if (candidates.length === 0) {
|
|
1038
|
-
candidates = [
|
|
1039
|
-
...ctx.getNodesByKind('struct'),
|
|
1040
|
-
...ctx.getNodesByKind('component'),
|
|
1041
|
-
...ctx.getNodesByKind('function'),
|
|
1042
|
-
...ctx.getNodesByKind('method'),
|
|
1043
|
-
].filter((n) => n.name === name && isArkuiChildTarget(n));
|
|
1044
|
-
}
|
|
1045
1042
|
candidates = candidates.filter((n) => n.id !== parentId);
|
|
1046
1043
|
// Dedupe by id — name index can surface the same row twice across caches.
|
|
1047
1044
|
candidates = [...new Map(candidates.map((n) => [n.id, n])).values()];
|
|
@@ -30,6 +30,11 @@ export declare function isKnownLanguageFamily(lang: string): boolean;
|
|
|
30
30
|
* returns `false` here and is left alone.
|
|
31
31
|
*/
|
|
32
32
|
export declare function crossesKnownFamily(a: string, b: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Spec 0066: resolve a return/param type name to a unique type node.
|
|
35
|
+
* Ambiguous same-named types → no edge (wrong type hub is worse than none).
|
|
36
|
+
*/
|
|
37
|
+
export declare function matchTypeEdge(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
|
|
33
38
|
/**
|
|
34
39
|
* Resolve a function-as-value reference (#756) — a function name used as a
|
|
35
40
|
* callback/function-pointer value (`register(handler)`, `o->cb = handler`,
|