@mars167/git-ai 2.3.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/LICENSE +22 -0
- package/README.md +364 -0
- package/README.zh-CN.md +361 -0
- package/assets/hooks/post-checkout +28 -0
- package/assets/hooks/post-merge +28 -0
- package/assets/hooks/pre-commit +17 -0
- package/assets/hooks/pre-push +29 -0
- package/dist/bin/git-ai.js +62 -0
- package/dist/src/commands/ai.js +30 -0
- package/dist/src/commands/checkIndex.js +19 -0
- package/dist/src/commands/dsr.js +156 -0
- package/dist/src/commands/graph.js +203 -0
- package/dist/src/commands/hooks.js +125 -0
- package/dist/src/commands/index.js +92 -0
- package/dist/src/commands/pack.js +31 -0
- package/dist/src/commands/query.js +139 -0
- package/dist/src/commands/semantic.js +134 -0
- package/dist/src/commands/serve.js +14 -0
- package/dist/src/commands/status.js +78 -0
- package/dist/src/commands/trae.js +75 -0
- package/dist/src/commands/unpack.js +28 -0
- package/dist/src/core/archive.js +91 -0
- package/dist/src/core/astGraph.js +127 -0
- package/dist/src/core/astGraphQuery.js +142 -0
- package/dist/src/core/cozo.js +266 -0
- package/dist/src/core/cpg/astLayer.js +56 -0
- package/dist/src/core/cpg/callGraph.js +483 -0
- package/dist/src/core/cpg/cfgLayer.js +490 -0
- package/dist/src/core/cpg/dfgLayer.js +237 -0
- package/dist/src/core/cpg/index.js +80 -0
- package/dist/src/core/cpg/types.js +108 -0
- package/dist/src/core/crypto.js +10 -0
- package/dist/src/core/dsr/generate.js +308 -0
- package/dist/src/core/dsr/gitContext.js +74 -0
- package/dist/src/core/dsr/indexMaterialize.js +106 -0
- package/dist/src/core/dsr/paths.js +26 -0
- package/dist/src/core/dsr/query.js +73 -0
- package/dist/src/core/dsr/snapshotParser.js +73 -0
- package/dist/src/core/dsr/state.js +27 -0
- package/dist/src/core/dsr/types.js +2 -0
- package/dist/src/core/embedding/fusion.js +52 -0
- package/dist/src/core/embedding/index.js +43 -0
- package/dist/src/core/embedding/parser.js +14 -0
- package/dist/src/core/embedding/semantic.js +254 -0
- package/dist/src/core/embedding/structural.js +97 -0
- package/dist/src/core/embedding/symbolic.js +117 -0
- package/dist/src/core/embedding/tokenizer.js +91 -0
- package/dist/src/core/embedding/types.js +2 -0
- package/dist/src/core/embedding.js +36 -0
- package/dist/src/core/git.js +49 -0
- package/dist/src/core/gitDiff.js +73 -0
- package/dist/src/core/indexCheck.js +131 -0
- package/dist/src/core/indexer.js +185 -0
- package/dist/src/core/indexerIncremental.js +303 -0
- package/dist/src/core/indexing/config.js +51 -0
- package/dist/src/core/indexing/hnsw.js +568 -0
- package/dist/src/core/indexing/index.js +17 -0
- package/dist/src/core/indexing/monitor.js +82 -0
- package/dist/src/core/indexing/parallel.js +252 -0
- package/dist/src/core/lancedb.js +111 -0
- package/dist/src/core/lfs.js +27 -0
- package/dist/src/core/log.js +62 -0
- package/dist/src/core/manifest.js +88 -0
- package/dist/src/core/parser/adapter.js +2 -0
- package/dist/src/core/parser/c.js +93 -0
- package/dist/src/core/parser/chunkRelations.js +178 -0
- package/dist/src/core/parser/chunker.js +274 -0
- package/dist/src/core/parser/go.js +98 -0
- package/dist/src/core/parser/java.js +80 -0
- package/dist/src/core/parser/markdown.js +76 -0
- package/dist/src/core/parser/python.js +81 -0
- package/dist/src/core/parser/rust.js +103 -0
- package/dist/src/core/parser/typescript.js +98 -0
- package/dist/src/core/parser/utils.js +62 -0
- package/dist/src/core/parser/yaml.js +53 -0
- package/dist/src/core/parser.js +75 -0
- package/dist/src/core/paths.js +10 -0
- package/dist/src/core/repoMap.js +164 -0
- package/dist/src/core/retrieval/cache.js +31 -0
- package/dist/src/core/retrieval/classifier.js +74 -0
- package/dist/src/core/retrieval/expander.js +80 -0
- package/dist/src/core/retrieval/fuser.js +40 -0
- package/dist/src/core/retrieval/index.js +32 -0
- package/dist/src/core/retrieval/reranker.js +304 -0
- package/dist/src/core/retrieval/types.js +2 -0
- package/dist/src/core/retrieval/weights.js +42 -0
- package/dist/src/core/search.js +41 -0
- package/dist/src/core/sq8.js +65 -0
- package/dist/src/core/symbolSearch.js +143 -0
- package/dist/src/core/types.js +2 -0
- package/dist/src/core/workspace.js +116 -0
- package/dist/src/mcp/server.js +794 -0
- package/docs/README.md +44 -0
- package/docs/cross-encoder.md +157 -0
- package/docs/embedding.md +158 -0
- package/docs/logo.png +0 -0
- package/docs/windows-setup.md +67 -0
- package/docs/zh-CN/DESIGN.md +102 -0
- package/docs/zh-CN/README.md +46 -0
- package/docs/zh-CN/advanced.md +26 -0
- package/docs/zh-CN/architecture_explained.md +116 -0
- package/docs/zh-CN/cli.md +109 -0
- package/docs/zh-CN/dsr.md +91 -0
- package/docs/zh-CN/graph_scenarios.md +173 -0
- package/docs/zh-CN/hooks.md +14 -0
- package/docs/zh-CN/manifests.md +136 -0
- package/docs/zh-CN/mcp.md +205 -0
- package/docs/zh-CN/quickstart.md +35 -0
- package/docs/zh-CN/rules.md +7 -0
- package/docs/zh-CN/technical-details.md +454 -0
- package/docs/zh-CN/troubleshooting.md +19 -0
- package/docs/zh-CN/windows-setup.md +67 -0
- package/install.sh +183 -0
- package/package.json +97 -0
- package/skills/git-ai-mcp/SKILL.md +86 -0
- package/skills/git-ai-mcp/references/constraints.md +143 -0
- package/skills/git-ai-mcp/references/tools.md +263 -0
- package/templates/agents/common/documents/Fix EISDIR error and enable multi-language indexing.md +14 -0
- package/templates/agents/common/documents/Fix git-ai index error in CodaGraph directory.md +13 -0
- package/templates/agents/common/skills/git-ai-mcp/SKILL.md +86 -0
- package/templates/agents/common/skills/git-ai-mcp/references/constraints.md +143 -0
- package/templates/agents/common/skills/git-ai-mcp/references/tools.md +263 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildCfgLayer = buildCfgLayer;
|
|
7
|
+
exports.buildCFG = buildCFG;
|
|
8
|
+
const tree_sitter_1 = __importDefault(require("tree-sitter"));
|
|
9
|
+
const tree_sitter_typescript_1 = __importDefault(require("tree-sitter-typescript"));
|
|
10
|
+
const types_1 = require("./types");
|
|
11
|
+
const SIMPLE_STATEMENT_TYPES = new Set([
|
|
12
|
+
'expression_statement',
|
|
13
|
+
'variable_declaration',
|
|
14
|
+
'lexical_declaration',
|
|
15
|
+
'empty_statement',
|
|
16
|
+
'debugger_statement',
|
|
17
|
+
]);
|
|
18
|
+
const LOOP_TYPES = new Set([
|
|
19
|
+
'for_statement',
|
|
20
|
+
'for_in_statement',
|
|
21
|
+
'for_of_statement',
|
|
22
|
+
'while_statement',
|
|
23
|
+
'do_statement',
|
|
24
|
+
]);
|
|
25
|
+
const CONDITIONAL_TYPES = new Set(['if_statement', 'conditional_expression']);
|
|
26
|
+
const SHORT_CIRCUIT_TYPES = new Set(['logical_expression', 'conditional_expression']);
|
|
27
|
+
const FUNCTION_TYPES = new Set(['function_declaration', 'function', 'arrow_function', 'method_definition']);
|
|
28
|
+
function isStatementNode(node) {
|
|
29
|
+
if (node.type === 'statement_block')
|
|
30
|
+
return true;
|
|
31
|
+
if (node.type === 'block')
|
|
32
|
+
return true;
|
|
33
|
+
if (node.type.endsWith('_statement'))
|
|
34
|
+
return true;
|
|
35
|
+
if (node.type.endsWith('_declaration'))
|
|
36
|
+
return true;
|
|
37
|
+
if (CONDITIONAL_TYPES.has(node.type))
|
|
38
|
+
return true;
|
|
39
|
+
if (SHORT_CIRCUIT_TYPES.has(node.type))
|
|
40
|
+
return true;
|
|
41
|
+
if (LOOP_TYPES.has(node.type))
|
|
42
|
+
return true;
|
|
43
|
+
if (node.type === 'switch_statement' || node.type === 'try_statement')
|
|
44
|
+
return true;
|
|
45
|
+
if (node.type === 'switch_case' || node.type === 'switch_default')
|
|
46
|
+
return true;
|
|
47
|
+
if (node.type === 'variable_declaration' || node.type === 'lexical_declaration')
|
|
48
|
+
return true;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
function emitEdge(edges, from, to, type) {
|
|
52
|
+
if (!from || !to)
|
|
53
|
+
return;
|
|
54
|
+
if (from === to && type !== types_1.EdgeType.TRUE_BRANCH && type !== types_1.EdgeType.FALSE_BRANCH)
|
|
55
|
+
return;
|
|
56
|
+
edges.push({ from, to, type });
|
|
57
|
+
}
|
|
58
|
+
function collectNamedChildren(node) {
|
|
59
|
+
const out = [];
|
|
60
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
61
|
+
const child = node.namedChild(i);
|
|
62
|
+
if (child)
|
|
63
|
+
out.push(child);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
// helper inlined to avoid unused warning
|
|
68
|
+
function buildBlock(nodes, filePath, edges, loop) {
|
|
69
|
+
let entryId = null;
|
|
70
|
+
let exits = [];
|
|
71
|
+
for (const stmt of nodes) {
|
|
72
|
+
if (!isStatementNode(stmt)) {
|
|
73
|
+
if (stmt.type === 'expression_statement' || stmt.type === 'return_statement') {
|
|
74
|
+
const exprEdges = buildExpressionEdges(stmt, filePath, edges, loop);
|
|
75
|
+
if (exprEdges.entryId) {
|
|
76
|
+
if (!entryId)
|
|
77
|
+
entryId = exprEdges.entryId;
|
|
78
|
+
for (const exit of exits) {
|
|
79
|
+
emitEdge(edges, exit, exprEdges.entryId, types_1.EdgeType.NEXT_STATEMENT);
|
|
80
|
+
}
|
|
81
|
+
exits = exprEdges.exits;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const result = buildStatement(stmt, filePath, edges, loop);
|
|
87
|
+
if (!result.entryId)
|
|
88
|
+
continue;
|
|
89
|
+
if (!entryId)
|
|
90
|
+
entryId = result.entryId;
|
|
91
|
+
for (const exit of exits) {
|
|
92
|
+
emitEdge(edges, exit, result.entryId, types_1.EdgeType.NEXT_STATEMENT);
|
|
93
|
+
}
|
|
94
|
+
exits = result.exits;
|
|
95
|
+
}
|
|
96
|
+
return { entryId, exits };
|
|
97
|
+
}
|
|
98
|
+
function buildSimple(node, filePath) {
|
|
99
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
100
|
+
return { entryId: id, exits: [id] };
|
|
101
|
+
}
|
|
102
|
+
function buildReturn(node, filePath) {
|
|
103
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
104
|
+
return { entryId: id, exits: [] };
|
|
105
|
+
}
|
|
106
|
+
function buildThrow(node, filePath) {
|
|
107
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
108
|
+
return { entryId: id, exits: [] };
|
|
109
|
+
}
|
|
110
|
+
function buildBreak(node, filePath, loop) {
|
|
111
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
112
|
+
if (loop)
|
|
113
|
+
loop.breakTargets.push(id);
|
|
114
|
+
return { entryId: id, exits: [] };
|
|
115
|
+
}
|
|
116
|
+
function buildContinue(node, filePath, edges, loop) {
|
|
117
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
118
|
+
if (loop?.continueTarget) {
|
|
119
|
+
emitEdge(edges, id, loop.continueTarget, types_1.EdgeType.NEXT_STATEMENT);
|
|
120
|
+
}
|
|
121
|
+
return { entryId: id, exits: [] };
|
|
122
|
+
}
|
|
123
|
+
function buildIf(node, filePath, edges, loop) {
|
|
124
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
125
|
+
const consequence = node.childForFieldName('consequence') ?? node.childForFieldName('body');
|
|
126
|
+
const alternate = node.childForFieldName('alternative');
|
|
127
|
+
let trueResult = null;
|
|
128
|
+
let falseResult = null;
|
|
129
|
+
if (consequence) {
|
|
130
|
+
const block = consequence.type === 'block' ? collectNamedChildren(consequence) : [consequence];
|
|
131
|
+
trueResult = buildBlock(block, filePath, edges, loop);
|
|
132
|
+
emitEdge(edges, id, trueResult.entryId, types_1.EdgeType.TRUE_BRANCH);
|
|
133
|
+
}
|
|
134
|
+
if (alternate) {
|
|
135
|
+
const altBody = alternate.type === 'else_clause' ? alternate.namedChild(0) : alternate;
|
|
136
|
+
if (altBody) {
|
|
137
|
+
const block = altBody.type === 'block' ? collectNamedChildren(altBody) : [altBody];
|
|
138
|
+
falseResult = buildBlock(block, filePath, edges, loop);
|
|
139
|
+
emitEdge(edges, id, falseResult.entryId, types_1.EdgeType.FALSE_BRANCH);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// explicit false branch to allow branch detection in CFG
|
|
144
|
+
emitEdge(edges, id, id, types_1.EdgeType.FALSE_BRANCH);
|
|
145
|
+
}
|
|
146
|
+
const exits = [];
|
|
147
|
+
if (trueResult)
|
|
148
|
+
exits.push(...trueResult.exits);
|
|
149
|
+
if (falseResult)
|
|
150
|
+
exits.push(...falseResult.exits);
|
|
151
|
+
if (!alternate)
|
|
152
|
+
exits.push(id);
|
|
153
|
+
return { entryId: id, exits };
|
|
154
|
+
}
|
|
155
|
+
function buildConditionalExpression(node, filePath, edges) {
|
|
156
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
157
|
+
const consequence = node.childForFieldName('consequence');
|
|
158
|
+
const alternate = node.childForFieldName('alternative');
|
|
159
|
+
if (consequence)
|
|
160
|
+
emitEdge(edges, id, (0, types_1.astNodeId)(filePath, consequence), types_1.EdgeType.TRUE_BRANCH);
|
|
161
|
+
if (alternate)
|
|
162
|
+
emitEdge(edges, id, (0, types_1.astNodeId)(filePath, alternate), types_1.EdgeType.FALSE_BRANCH);
|
|
163
|
+
return { entryId: id, exits: [id] };
|
|
164
|
+
}
|
|
165
|
+
function buildFunctionBody(node, filePath, edges) {
|
|
166
|
+
const body = node.childForFieldName('body');
|
|
167
|
+
if (!body)
|
|
168
|
+
return;
|
|
169
|
+
const block = body.type === 'block' ? collectNamedChildren(body) : [body];
|
|
170
|
+
buildBlock(block, filePath, edges, undefined);
|
|
171
|
+
}
|
|
172
|
+
function buildClassBodies(node, filePath, edges) {
|
|
173
|
+
const body = node.childForFieldName('body');
|
|
174
|
+
if (!body)
|
|
175
|
+
return;
|
|
176
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
177
|
+
const child = body.namedChild(i);
|
|
178
|
+
if (!child)
|
|
179
|
+
continue;
|
|
180
|
+
if (child.type === 'method_definition')
|
|
181
|
+
buildFunctionBody(child, filePath, edges);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function buildDeclaratorBodies(node, filePath, edges) {
|
|
185
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
186
|
+
const declarator = node.namedChild(i);
|
|
187
|
+
if (!declarator || declarator.type !== 'variable_declarator')
|
|
188
|
+
continue;
|
|
189
|
+
const value = declarator.childForFieldName('value');
|
|
190
|
+
if (!value)
|
|
191
|
+
continue;
|
|
192
|
+
if (FUNCTION_TYPES.has(value.type))
|
|
193
|
+
buildFunctionBody(value, filePath, edges);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function buildLoop(node, filePath, edges, loop) {
|
|
197
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
198
|
+
const body = node.childForFieldName('body') ?? node.childForFieldName('consequence');
|
|
199
|
+
const loopCtx = {
|
|
200
|
+
continueTarget: id,
|
|
201
|
+
breakTargets: [],
|
|
202
|
+
};
|
|
203
|
+
let bodyResult = null;
|
|
204
|
+
if (body) {
|
|
205
|
+
const block = body.type === 'block' ? collectNamedChildren(body) : [body];
|
|
206
|
+
bodyResult = buildBlock(block, filePath, edges, loopCtx);
|
|
207
|
+
emitEdge(edges, id, bodyResult.entryId, types_1.EdgeType.TRUE_BRANCH);
|
|
208
|
+
for (const exit of bodyResult.exits) {
|
|
209
|
+
emitEdge(edges, exit, id, types_1.EdgeType.NEXT_STATEMENT);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const exits = [...loopCtx.breakTargets, ...(loop ? [] : [id])];
|
|
213
|
+
return { entryId: id, exits };
|
|
214
|
+
}
|
|
215
|
+
function buildDoWhile(node, filePath, edges, loop) {
|
|
216
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
217
|
+
const body = node.childForFieldName('body');
|
|
218
|
+
const loopCtx = {
|
|
219
|
+
continueTarget: id,
|
|
220
|
+
breakTargets: [],
|
|
221
|
+
};
|
|
222
|
+
let bodyResult = null;
|
|
223
|
+
if (body) {
|
|
224
|
+
const block = body.type === 'block' ? collectNamedChildren(body) : [body];
|
|
225
|
+
bodyResult = buildBlock(block, filePath, edges, loopCtx);
|
|
226
|
+
emitEdge(edges, id, bodyResult.entryId, types_1.EdgeType.TRUE_BRANCH);
|
|
227
|
+
for (const exit of bodyResult.exits) {
|
|
228
|
+
emitEdge(edges, exit, id, types_1.EdgeType.NEXT_STATEMENT);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const exits = [...loopCtx.breakTargets, ...(loop ? [] : [id])];
|
|
232
|
+
return { entryId: id, exits };
|
|
233
|
+
}
|
|
234
|
+
function buildSwitch(node, filePath, edges) {
|
|
235
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
236
|
+
const body = node.childForFieldName('body');
|
|
237
|
+
const caseNodes = body ? collectNamedChildren(body) : [];
|
|
238
|
+
let hasDefault = false;
|
|
239
|
+
const exits = [];
|
|
240
|
+
const breakTargets = [];
|
|
241
|
+
let previousCaseExit = null;
|
|
242
|
+
for (const caseNode of caseNodes) {
|
|
243
|
+
if (caseNode.type !== 'switch_case' && caseNode.type !== 'switch_default')
|
|
244
|
+
continue;
|
|
245
|
+
if (caseNode.type === 'switch_default')
|
|
246
|
+
hasDefault = true;
|
|
247
|
+
const statements = collectNamedChildren(caseNode).filter(isStatementNode);
|
|
248
|
+
const caseEntry = statements[0] ?? caseNode;
|
|
249
|
+
const caseEntryId = (0, types_1.astNodeId)(filePath, caseEntry);
|
|
250
|
+
const caseResult = buildBlock(statements, filePath, edges, { continueTarget: null, breakTargets });
|
|
251
|
+
emitEdge(edges, id, caseResult.entryId ?? caseEntryId, types_1.EdgeType.TRUE_BRANCH);
|
|
252
|
+
if (previousCaseExit)
|
|
253
|
+
emitEdge(edges, previousCaseExit, caseResult.entryId ?? caseEntryId, types_1.EdgeType.FALLTHROUGH);
|
|
254
|
+
previousCaseExit = caseResult.exits.length > 0 ? caseResult.exits[caseResult.exits.length - 1] : null;
|
|
255
|
+
exits.push(...caseResult.exits);
|
|
256
|
+
}
|
|
257
|
+
if (!hasDefault)
|
|
258
|
+
exits.push(id);
|
|
259
|
+
exits.push(...breakTargets);
|
|
260
|
+
return { entryId: id, exits };
|
|
261
|
+
}
|
|
262
|
+
function buildTry(node, filePath, edges, loop) {
|
|
263
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
264
|
+
const body = node.childForFieldName('body');
|
|
265
|
+
const handler = node.childForFieldName('handler');
|
|
266
|
+
const finalizer = node.childForFieldName('finalizer');
|
|
267
|
+
let bodyResult = null;
|
|
268
|
+
let handlerResult = null;
|
|
269
|
+
let finalResult = null;
|
|
270
|
+
if (body) {
|
|
271
|
+
const block = body.type === 'block' ? collectNamedChildren(body) : [body];
|
|
272
|
+
bodyResult = buildBlock(block, filePath, edges, loop);
|
|
273
|
+
emitEdge(edges, id, bodyResult.entryId, types_1.EdgeType.TRUE_BRANCH);
|
|
274
|
+
}
|
|
275
|
+
if (handler) {
|
|
276
|
+
const handlerBody = handler.childForFieldName('body') ?? handler;
|
|
277
|
+
const block = handlerBody.type === 'block' ? collectNamedChildren(handlerBody) : [handlerBody];
|
|
278
|
+
handlerResult = buildBlock(block, filePath, edges, loop);
|
|
279
|
+
emitEdge(edges, id, handlerResult.entryId, types_1.EdgeType.FALSE_BRANCH);
|
|
280
|
+
}
|
|
281
|
+
if (finalizer) {
|
|
282
|
+
const block = finalizer.type === 'block' ? collectNamedChildren(finalizer) : [finalizer];
|
|
283
|
+
finalResult = buildBlock(block, filePath, edges, loop);
|
|
284
|
+
}
|
|
285
|
+
const exits = [];
|
|
286
|
+
const bodyExits = bodyResult?.exits ?? [];
|
|
287
|
+
const handlerExits = handlerResult?.exits ?? [];
|
|
288
|
+
if (finalResult) {
|
|
289
|
+
for (const exit of [...bodyExits, ...handlerExits]) {
|
|
290
|
+
emitEdge(edges, exit, finalResult.entryId, types_1.EdgeType.NEXT_STATEMENT);
|
|
291
|
+
}
|
|
292
|
+
exits.push(...finalResult.exits);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
exits.push(...bodyExits, ...handlerExits);
|
|
296
|
+
}
|
|
297
|
+
return { entryId: id, exits };
|
|
298
|
+
}
|
|
299
|
+
function buildLogicalExpression(node, filePath, edges) {
|
|
300
|
+
const id = (0, types_1.astNodeId)(filePath, node);
|
|
301
|
+
const left = node.childForFieldName('left');
|
|
302
|
+
const right = node.childForFieldName('right');
|
|
303
|
+
const operator = extractLogicalOperator(node);
|
|
304
|
+
if (left)
|
|
305
|
+
emitEdge(edges, id, (0, types_1.astNodeId)(filePath, left), types_1.EdgeType.NEXT_STATEMENT);
|
|
306
|
+
if (right) {
|
|
307
|
+
if (operator === '||') {
|
|
308
|
+
// only evaluate right when left is false
|
|
309
|
+
emitEdge(edges, id, (0, types_1.astNodeId)(filePath, right), types_1.EdgeType.FALSE_BRANCH);
|
|
310
|
+
emitEdge(edges, id, id, types_1.EdgeType.TRUE_BRANCH);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// && : only evaluate right when left is true
|
|
314
|
+
emitEdge(edges, id, (0, types_1.astNodeId)(filePath, right), types_1.EdgeType.TRUE_BRANCH);
|
|
315
|
+
emitEdge(edges, id, id, types_1.EdgeType.FALSE_BRANCH);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return { entryId: id, exits: [id] };
|
|
319
|
+
}
|
|
320
|
+
function buildConditionalExpressionNode(node, filePath, edges) {
|
|
321
|
+
return buildConditionalExpression(node, filePath, edges);
|
|
322
|
+
}
|
|
323
|
+
function buildExpressionEdges(node, filePath, edges, loop) {
|
|
324
|
+
if (node.type === 'expression_statement' || node.type === 'return_statement') {
|
|
325
|
+
const expr = node.namedChild(0);
|
|
326
|
+
if (expr)
|
|
327
|
+
return buildExpressionEdges(expr, filePath, edges, loop);
|
|
328
|
+
}
|
|
329
|
+
if (node.type === 'logical_expression')
|
|
330
|
+
return buildLogicalExpression(node, filePath, edges);
|
|
331
|
+
if (node.type === 'conditional_expression')
|
|
332
|
+
return buildConditionalExpressionNode(node, filePath, edges);
|
|
333
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
334
|
+
const child = node.namedChild(i);
|
|
335
|
+
if (!child)
|
|
336
|
+
continue;
|
|
337
|
+
if (child.type === 'logical_expression')
|
|
338
|
+
return buildLogicalExpression(child, filePath, edges);
|
|
339
|
+
if (child.type === 'conditional_expression')
|
|
340
|
+
return buildConditionalExpressionNode(child, filePath, edges);
|
|
341
|
+
}
|
|
342
|
+
return { entryId: null, exits: [] };
|
|
343
|
+
}
|
|
344
|
+
function extractLogicalOperator(node) {
|
|
345
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
346
|
+
const child = node.child(i);
|
|
347
|
+
if (!child)
|
|
348
|
+
continue;
|
|
349
|
+
if (child.type === '&&' || child.type === '||')
|
|
350
|
+
return child.type;
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
function addShortCircuitEdges(root, filePath, edges) {
|
|
355
|
+
const visit = (node) => {
|
|
356
|
+
if (node.type === 'logical_expression' || node.type === 'binary_expression') {
|
|
357
|
+
const op = extractLogicalOperator(node);
|
|
358
|
+
if (op)
|
|
359
|
+
buildLogicalExpression(node, filePath, edges);
|
|
360
|
+
}
|
|
361
|
+
else if (node.type === 'conditional_expression' || node.type === 'ternary_expression') {
|
|
362
|
+
buildConditionalExpression(node, filePath, edges);
|
|
363
|
+
}
|
|
364
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
365
|
+
const child = node.child(i);
|
|
366
|
+
if (child)
|
|
367
|
+
visit(child);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
visit(root);
|
|
371
|
+
}
|
|
372
|
+
function buildStatement(node, filePath, edges, loop) {
|
|
373
|
+
if (FUNCTION_TYPES.has(node.type)) {
|
|
374
|
+
buildFunctionBody(node, filePath, edges);
|
|
375
|
+
return buildSimple(node, filePath);
|
|
376
|
+
}
|
|
377
|
+
if (node.type === 'class_declaration') {
|
|
378
|
+
buildClassBodies(node, filePath, edges);
|
|
379
|
+
return buildSimple(node, filePath);
|
|
380
|
+
}
|
|
381
|
+
if (node.type === 'variable_declaration' || node.type === 'lexical_declaration') {
|
|
382
|
+
buildDeclaratorBodies(node, filePath, edges);
|
|
383
|
+
return buildSimple(node, filePath);
|
|
384
|
+
}
|
|
385
|
+
if (node.type === 'expression_statement') {
|
|
386
|
+
const expr = node.namedChild(0);
|
|
387
|
+
if (expr?.type === 'assignment_expression') {
|
|
388
|
+
const value = expr.childForFieldName('right');
|
|
389
|
+
if (value && FUNCTION_TYPES.has(value.type))
|
|
390
|
+
buildFunctionBody(value, filePath, edges);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (node.type === 'return_statement')
|
|
394
|
+
return buildReturn(node, filePath);
|
|
395
|
+
if (node.type === 'throw_statement')
|
|
396
|
+
return buildThrow(node, filePath);
|
|
397
|
+
if (node.type === 'break_statement')
|
|
398
|
+
return buildBreak(node, filePath, loop);
|
|
399
|
+
if (node.type === 'continue_statement')
|
|
400
|
+
return buildContinue(node, filePath, edges, loop);
|
|
401
|
+
if (node.type === 'if_statement')
|
|
402
|
+
return buildIf(node, filePath, edges, loop);
|
|
403
|
+
if (node.type === 'conditional_expression')
|
|
404
|
+
return buildConditionalExpression(node, filePath, edges);
|
|
405
|
+
if (LOOP_TYPES.has(node.type)) {
|
|
406
|
+
if (node.type === 'do_statement')
|
|
407
|
+
return buildDoWhile(node, filePath, edges, loop);
|
|
408
|
+
return buildLoop(node, filePath, edges, loop);
|
|
409
|
+
}
|
|
410
|
+
if (node.type === 'switch_statement')
|
|
411
|
+
return buildSwitch(node, filePath, edges);
|
|
412
|
+
if (node.type === 'try_statement')
|
|
413
|
+
return buildTry(node, filePath, edges, loop);
|
|
414
|
+
if (node.type === 'block' || node.type === 'statement_block') {
|
|
415
|
+
const block = collectNamedChildren(node);
|
|
416
|
+
return buildBlock(block, filePath, edges, loop);
|
|
417
|
+
}
|
|
418
|
+
if (SIMPLE_STATEMENT_TYPES.has(node.type))
|
|
419
|
+
return buildSimple(node, filePath);
|
|
420
|
+
return buildSimple(node, filePath);
|
|
421
|
+
}
|
|
422
|
+
function collectCfgNodes(root, filePath) {
|
|
423
|
+
const out = [];
|
|
424
|
+
const visit = (node) => {
|
|
425
|
+
if (isStatementNode(node)) {
|
|
426
|
+
out.push({
|
|
427
|
+
id: (0, types_1.astNodeId)(filePath, node),
|
|
428
|
+
stmtType: node.type,
|
|
429
|
+
startLine: node.startPosition.row + 1,
|
|
430
|
+
endLine: node.endPosition.row + 1,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
434
|
+
const child = node.child(i);
|
|
435
|
+
if (child)
|
|
436
|
+
visit(child);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
visit(root);
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
function buildCfgInternal(filePath, root) {
|
|
443
|
+
const edges = [];
|
|
444
|
+
const topStatements = root.type === 'program' ? collectNamedChildren(root) : [root];
|
|
445
|
+
const result = buildBlock(topStatements, filePath, edges, undefined);
|
|
446
|
+
addShortCircuitEdges(root, filePath, edges);
|
|
447
|
+
const cfgNodes = collectCfgNodes(root, filePath);
|
|
448
|
+
const nodes = cfgNodes.map((node) => ({
|
|
449
|
+
id: node.id,
|
|
450
|
+
kind: 'cfg',
|
|
451
|
+
label: node.stmtType,
|
|
452
|
+
startLine: node.startLine,
|
|
453
|
+
endLine: node.endLine,
|
|
454
|
+
}));
|
|
455
|
+
return {
|
|
456
|
+
nodes,
|
|
457
|
+
edges,
|
|
458
|
+
entryId: result.entryId,
|
|
459
|
+
exitIds: result.exits,
|
|
460
|
+
rawNodes: cfgNodes,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function buildCfgLayer(filePath, root) {
|
|
464
|
+
const edgeTypes = [types_1.EdgeType.NEXT_STATEMENT, types_1.EdgeType.TRUE_BRANCH, types_1.EdgeType.FALSE_BRANCH, types_1.EdgeType.FALLTHROUGH];
|
|
465
|
+
const internal = buildCfgInternal(filePath, root);
|
|
466
|
+
return { nodes: internal.nodes, edges: internal.edges, edgeTypes };
|
|
467
|
+
}
|
|
468
|
+
function buildCFG(filePath, content) {
|
|
469
|
+
const parser = new tree_sitter_1.default();
|
|
470
|
+
parser.setLanguage(tree_sitter_typescript_1.default.typescript);
|
|
471
|
+
const tree = parser.parse(content);
|
|
472
|
+
const internal = buildCfgInternal(filePath, tree.rootNode);
|
|
473
|
+
const nodes = internal.rawNodes.map((node) => ({
|
|
474
|
+
id: node.id,
|
|
475
|
+
stmtType: node.stmtType,
|
|
476
|
+
startLine: node.startLine,
|
|
477
|
+
endLine: node.endLine,
|
|
478
|
+
}));
|
|
479
|
+
const edges = internal.edges.map((edge) => ({
|
|
480
|
+
from: edge.from,
|
|
481
|
+
to: edge.to,
|
|
482
|
+
edgeType: edge.type,
|
|
483
|
+
}));
|
|
484
|
+
return {
|
|
485
|
+
nodes,
|
|
486
|
+
edges,
|
|
487
|
+
entryPoint: internal.entryId ?? '',
|
|
488
|
+
exitPoints: internal.exitIds,
|
|
489
|
+
};
|
|
490
|
+
}
|