@sun-asterisk/impact-analyzer 1.0.4 → 1.0.6
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/.github/copilot-instructions.md +116 -0
- package/.github/prompts/README.md +91 -0
- package/.github/prompts/task-001-refactor.prompt.md +241 -0
- package/.specify/bugs/bug-001-database-detector.md +222 -0
- package/.specify/bugs/bug-002-database-detector.md +478 -0
- package/.specify/bugs/bug-003-multiline-detection.md +527 -0
- package/.specify/plans/architecture.md +186 -0
- package/.specify/specs/features/api-impact-detection.md +317 -0
- package/.specify/specs/features/component-impact-detection.md +263 -0
- package/.specify/specs/features/database-impact-detection.md +247 -0
- package/.specify/tasks/task-001-refactor-api-detector.md +284 -0
- package/.specify/tasks/task-002-database-detector.md +593 -0
- package/.specify/tasks/task-003-component-detector.md +0 -0
- package/.specify/tasks/task-004-report.md +484 -0
- package/README.md +13 -19
- package/core/detectors/database-detector.js +912 -0
- package/{modules → core}/detectors/endpoint-detector.js +11 -8
- package/{modules → core}/report-generator.js +102 -20
- package/core/utils/logger.js +12 -0
- package/index.js +6 -5
- package/package.json +1 -1
- package/modules/detectors/database-detector.js +0 -182
- /package/{modules → core}/change-detector.js +0 -0
- /package/{modules → core}/impact-analyzer.js +0 -0
- /package/{modules → core}/utils/ast-parser.js +0 -0
- /package/{modules → core}/utils/dependency-graph.js +0 -0
- /package/{modules → core}/utils/file-utils.js +0 -0
- /package/{modules → core}/utils/git-utils.js +0 -0
- /package/{modules → core}/utils/method-call-graph.js +0 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# BUG-002: Database Detector Cannot Detect Repository Injection in Services
|
|
2
|
+
|
|
3
|
+
**Bug ID:** BUG-002
|
|
4
|
+
**Status:** ✅ RESOLVED
|
|
5
|
+
**Priority:** HIGH
|
|
6
|
+
**Severity:** Critical
|
|
7
|
+
**Created:** 2025-12-26
|
|
8
|
+
**Updated:** 2025-12-26
|
|
9
|
+
**Resolved:** 2025-12-26
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Problem Statement
|
|
14
|
+
|
|
15
|
+
Database detector fails to detect database changes when repositories are injected into services via dependency injection (DI). The current implementation only detects repository methods through call graph traversal, missing direct repository usage in service classes.
|
|
16
|
+
|
|
17
|
+
### Observed Behavior
|
|
18
|
+
|
|
19
|
+
When a service uses an injected repository, database operations are **not detected**:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// ❌ Not detected:
|
|
23
|
+
export class UserService {
|
|
24
|
+
constructor(
|
|
25
|
+
@InjectRepository(UserEntity)
|
|
26
|
+
private readonly userRepository: Repository<UserEntity>,
|
|
27
|
+
) {}
|
|
28
|
+
|
|
29
|
+
async updateData() {
|
|
30
|
+
// This change is NOT detected
|
|
31
|
+
const currentData = this.userRepository
|
|
32
|
+
.createQueryBuilder('user')
|
|
33
|
+
.select([
|
|
34
|
+
'user.id',
|
|
35
|
+
'user.name',
|
|
36
|
+
'user.email' // <- Field added here
|
|
37
|
+
])
|
|
38
|
+
.innerJoin('posts', 'post', 'post.userId = user.id')
|
|
39
|
+
.where('user.status = :status', { status: 'active' })
|
|
40
|
+
.getRawMany();
|
|
41
|
+
|
|
42
|
+
// This is also NOT detected
|
|
43
|
+
await this.userRepository.update({ id: 1 }, { name: 'Updated' });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Expected Behavior
|
|
49
|
+
|
|
50
|
+
All database operations should be detected, regardless of whether they are in repository classes or injected into services.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Root Cause
|
|
55
|
+
|
|
56
|
+
**File:** `core/detectors/database-detector.js` (line ~263-289)
|
|
57
|
+
|
|
58
|
+
### Issue 1: Call Graph Dependency
|
|
59
|
+
|
|
60
|
+
The `findAffectedRepositoryMethods()` method relies on call graph traversal:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
findAffectedRepositoryMethods(changedMethods) {
|
|
64
|
+
const visited = new Set();
|
|
65
|
+
const repoMethods = [];
|
|
66
|
+
const queue = [...changedMethods];
|
|
67
|
+
|
|
68
|
+
while (queue.length > 0) {
|
|
69
|
+
const method = queue.shift();
|
|
70
|
+
|
|
71
|
+
// Only finds methods in repository files or with "repository" in class name
|
|
72
|
+
const isRepository = method.file.includes('repository') ||
|
|
73
|
+
(method.className && method.className.toLowerCase().includes('repository'));
|
|
74
|
+
|
|
75
|
+
if (isRepository) {
|
|
76
|
+
repoMethods.push(method);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const callers = this.methodCallGraph.getCallers(method);
|
|
80
|
+
queue.push(...callers);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return repoMethods;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Problems:**
|
|
88
|
+
1. ❌ Requires method call graph to connect service → repository
|
|
89
|
+
2. ❌ Only detects repository classes, not injected repository instances
|
|
90
|
+
3. ❌ Misses `this.userRepository` usage in services
|
|
91
|
+
4. ❌ Cannot detect direct query builder usage
|
|
92
|
+
|
|
93
|
+
### Issue 2: Missing Direct Detection
|
|
94
|
+
|
|
95
|
+
The `analyzeChangedCodeForDatabaseOps()` method only detects operations in changed lines:
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
// Only looks at added lines in diff
|
|
99
|
+
for (const [opName, opType] of Object.entries(typeOrmOps)) {
|
|
100
|
+
if (cleanLine.includes(`.${opName}(`)) {
|
|
101
|
+
// Detect operation
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Problems:**
|
|
107
|
+
1. ❌ Doesn't detect `@InjectRepository()` decorator
|
|
108
|
+
2. ❌ Doesn't track injected repository field names
|
|
109
|
+
3. ❌ Doesn't detect `this.repoName.operation()` patterns
|
|
110
|
+
4. ❌ Misses query builder chains
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Fix Strategy
|
|
115
|
+
|
|
116
|
+
### Approach
|
|
117
|
+
|
|
118
|
+
Enhance detection to work without call graph dependency. Directly analyze changed files for:
|
|
119
|
+
1. Repository injection patterns (`@InjectRepository`)
|
|
120
|
+
2. Repository field usage (`this.userRepository`)
|
|
121
|
+
3. Query builder patterns (`.createQueryBuilder()`, `.select()`, `.where()`)
|
|
122
|
+
4. Entity references to determine affected tables
|
|
123
|
+
|
|
124
|
+
### Implementation Steps
|
|
125
|
+
|
|
126
|
+
#### Step 1: Detect Repository Injection
|
|
127
|
+
|
|
128
|
+
Add method to detect injected repositories from constructor parameters:
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
/**
|
|
132
|
+
* Detect @InjectRepository decorators and extract entity information
|
|
133
|
+
*/
|
|
134
|
+
detectInjectedRepositories(content, filePath) {
|
|
135
|
+
const injectedRepos = [];
|
|
136
|
+
const lines = content.split('\n');
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < lines.length; i++) {
|
|
139
|
+
const line = lines[i];
|
|
140
|
+
|
|
141
|
+
// Pattern: @InjectRepository(UserEntity)
|
|
142
|
+
const injectMatch = line.match(/@InjectRepository\s*\(\s*(\w+Entity)\s*\)/);
|
|
143
|
+
if (injectMatch) {
|
|
144
|
+
const entityName = injectMatch[1];
|
|
145
|
+
|
|
146
|
+
// Look for field name in next few lines
|
|
147
|
+
// private readonly userRepository: Repository<UserEntity>
|
|
148
|
+
for (let j = i; j < Math.min(i + 3, lines.length); j++) {
|
|
149
|
+
const fieldMatch = lines[j].match(/(?:private|public|protected)\s+(?:readonly\s+)?(\w+)\s*:/);
|
|
150
|
+
if (fieldMatch) {
|
|
151
|
+
injectedRepos.push({
|
|
152
|
+
entityName: entityName,
|
|
153
|
+
fieldName: fieldMatch[1],
|
|
154
|
+
tableName: this.entityToTableName(entityName),
|
|
155
|
+
file: filePath
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return injectedRepos;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### Step 2: Detect Repository Usage in Changes
|
|
168
|
+
|
|
169
|
+
Enhance `analyzeChangedCodeForDatabaseOps()` to detect repository field usage:
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
analyzeChangedCodeForDatabaseOps(changedFile, databaseChanges) {
|
|
173
|
+
const diff = changedFile.diff || '';
|
|
174
|
+
const content = changedFile.content || '';
|
|
175
|
+
|
|
176
|
+
// Step 1: Detect injected repositories in file
|
|
177
|
+
const injectedRepos = this.detectInjectedRepositories(content, changedFile.path);
|
|
178
|
+
|
|
179
|
+
if (injectedRepos.length > 0) {
|
|
180
|
+
this.logger.verbose('DatabaseDetector',
|
|
181
|
+
`Found ${injectedRepos.length} injected repositories in ${changedFile.path}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Step 2: Analyze diff for repository usage
|
|
185
|
+
const lines = diff.split('\n');
|
|
186
|
+
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
if (!line.startsWith('+')) continue;
|
|
189
|
+
|
|
190
|
+
const addedLine = line.substring(1).trim();
|
|
191
|
+
|
|
192
|
+
// Check if line uses any injected repository
|
|
193
|
+
for (const repo of injectedRepos) {
|
|
194
|
+
// Pattern: this.userRepository.find(...)
|
|
195
|
+
if (addedLine.includes(`this.${repo.fieldName}.`)) {
|
|
196
|
+
this.detectOperationFromRepositoryUsage(
|
|
197
|
+
addedLine,
|
|
198
|
+
repo,
|
|
199
|
+
databaseChanges
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Step 3: Detect Query Builder Patterns
|
|
208
|
+
|
|
209
|
+
Add specific detection for query builder chains:
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
/**
|
|
213
|
+
* Detect database operations from repository field usage
|
|
214
|
+
*/
|
|
215
|
+
detectOperationFromRepositoryUsage(line, repo, databaseChanges) {
|
|
216
|
+
const operations = {
|
|
217
|
+
'createQueryBuilder': 'SELECT',
|
|
218
|
+
'find': 'SELECT',
|
|
219
|
+
'findOne': 'SELECT',
|
|
220
|
+
'update': 'UPDATE',
|
|
221
|
+
'insert': 'INSERT',
|
|
222
|
+
'delete': 'DELETE',
|
|
223
|
+
'save': 'INSERT/UPDATE',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Detect operation
|
|
227
|
+
let detectedOp = null;
|
|
228
|
+
for (const [method, opType] of Object.entries(operations)) {
|
|
229
|
+
if (line.includes(`.${method}(`)) {
|
|
230
|
+
detectedOp = opType;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (!detectedOp) return;
|
|
236
|
+
|
|
237
|
+
// Extract entity/table info
|
|
238
|
+
const tableName = repo.tableName;
|
|
239
|
+
|
|
240
|
+
if (!databaseChanges.tables.has(tableName)) {
|
|
241
|
+
databaseChanges.tables.set(tableName, {
|
|
242
|
+
entity: repo.entityName,
|
|
243
|
+
file: repo.file,
|
|
244
|
+
operations: new Set(),
|
|
245
|
+
fields: new Set(),
|
|
246
|
+
isEntityFile: false,
|
|
247
|
+
hasRelationChange: false,
|
|
248
|
+
changeSource: { path: repo.file }
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const tableData = databaseChanges.tables.get(tableName);
|
|
253
|
+
tableData.operations.add(detectedOp);
|
|
254
|
+
|
|
255
|
+
// Extract fields from query builder
|
|
256
|
+
this.extractFieldsFromQueryBuilder(line, tableData);
|
|
257
|
+
|
|
258
|
+
this.logger.verbose('DatabaseDetector',
|
|
259
|
+
`Detected ${detectedOp} on ${tableName} via ${repo.fieldName}`);
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### Step 4: Extract Fields from Query Builder
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
/**
|
|
267
|
+
* Extract field names from query builder select()
|
|
268
|
+
*/
|
|
269
|
+
extractFieldsFromQueryBuilder(line, tableData) {
|
|
270
|
+
// Pattern: .select(['user.id', 'user.name', 'user.email'])
|
|
271
|
+
const selectMatch = line.match(/\.select\s*\(\s*\[([^\]]+)\]/);
|
|
272
|
+
if (selectMatch) {
|
|
273
|
+
const fieldsStr = selectMatch[1];
|
|
274
|
+
const fieldMatches = fieldsStr.matchAll(/['"](?:\w+\.)?(\w+)['"]/g);
|
|
275
|
+
|
|
276
|
+
for (const match of fieldMatches) {
|
|
277
|
+
const fieldName = match[1];
|
|
278
|
+
if (fieldName !== 'delFlg') {
|
|
279
|
+
tableData.fields.add(fieldName);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Pattern: .where('user.name = :name', ...)
|
|
285
|
+
const whereMatch = line.match(/\.where\s*\(\s*['"](?:\w+\.)?(\w+)\s*[=<>]/);
|
|
286
|
+
if (whereMatch) {
|
|
287
|
+
const fieldName = whereMatch[1];
|
|
288
|
+
if (fieldName !== 'delFlg') {
|
|
289
|
+
tableData.fields.add(fieldName);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Verification
|
|
298
|
+
|
|
299
|
+
### Test Case 1: Injected Repository Detection
|
|
300
|
+
|
|
301
|
+
**Input:**
|
|
302
|
+
```typescript
|
|
303
|
+
+ export class UserService {
|
|
304
|
+
+ constructor(
|
|
305
|
+
+ @InjectRepository(UserEntity)
|
|
306
|
+
+ private readonly userRepository: Repository<UserEntity>,
|
|
307
|
+
+ ) {}
|
|
308
|
+
+
|
|
309
|
+
+ async updateData() {
|
|
310
|
+
+ await this.userRepository.update({ id: 1 }, { name: 'Updated' });
|
|
311
|
+
+ }
|
|
312
|
+
+ }
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Expected Output:**
|
|
316
|
+
```javascript
|
|
317
|
+
{
|
|
318
|
+
tableName: "user",
|
|
319
|
+
modelName: "UserEntity",
|
|
320
|
+
operations: ["UPDATE"],
|
|
321
|
+
fields: ["id", "name"],
|
|
322
|
+
severity: "medium"
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Test Case 2: Query Builder with Select
|
|
327
|
+
|
|
328
|
+
**Input:**
|
|
329
|
+
```typescript
|
|
330
|
+
+ const users = this.userRepository
|
|
331
|
+
+ .createQueryBuilder('user')
|
|
332
|
+
+ .select(['user.id', 'user.name', 'user.email'])
|
|
333
|
+
+ .where('user.status = :status', { status: 'active' })
|
|
334
|
+
+ .getRawMany();
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Expected Output:**
|
|
338
|
+
```javascript
|
|
339
|
+
{
|
|
340
|
+
tableName: "user",
|
|
341
|
+
modelName: "UserEntity",
|
|
342
|
+
operations: ["SELECT"],
|
|
343
|
+
fields: ["id", "name", "email", "status"],
|
|
344
|
+
severity: "low"
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Test Case 3: Multiple Injected Repositories
|
|
349
|
+
|
|
350
|
+
**Input:**
|
|
351
|
+
```typescript
|
|
352
|
+
+ export class PostService {
|
|
353
|
+
+ constructor(
|
|
354
|
+
+ @InjectRepository(PostEntity)
|
|
355
|
+
+ private readonly postRepository: Repository<PostEntity>,
|
|
356
|
+
+ @InjectRepository(UserEntity)
|
|
357
|
+
+ private readonly userRepository: Repository<UserEntity>,
|
|
358
|
+
+ ) {}
|
|
359
|
+
+
|
|
360
|
+
+ async updatePost() {
|
|
361
|
+
+ await this.postRepository.update({ id: 1 }, { title: 'New' });
|
|
362
|
+
+ await this.userRepository.findOne({ where: { id: 1 } });
|
|
363
|
+
+ }
|
|
364
|
+
+ }
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Expected Output:**
|
|
368
|
+
```javascript
|
|
369
|
+
[
|
|
370
|
+
{
|
|
371
|
+
tableName: "post",
|
|
372
|
+
operations: ["UPDATE"],
|
|
373
|
+
fields: ["id", "title"]
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
tableName: "user",
|
|
377
|
+
operations: ["SELECT"],
|
|
378
|
+
fields: ["id"]
|
|
379
|
+
}
|
|
380
|
+
]
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Impact Assessment
|
|
386
|
+
|
|
387
|
+
### Risk Level: **MEDIUM** ⚠️
|
|
388
|
+
|
|
389
|
+
**Why Medium Risk:**
|
|
390
|
+
- Adds new detection methods alongside existing logic
|
|
391
|
+
- Does not remove call graph detection (backward compatible)
|
|
392
|
+
- Extends functionality without breaking existing behavior
|
|
393
|
+
- More comprehensive detection may find more issues (good thing)
|
|
394
|
+
|
|
395
|
+
### Areas Affected
|
|
396
|
+
|
|
397
|
+
| Component | Change Type | Risk |
|
|
398
|
+
|-----------|-------------|------|
|
|
399
|
+
| `database-detector.js` | New methods added | Medium |
|
|
400
|
+
| `analyzeChangedCodeForDatabaseOps()` | Enhanced | Medium |
|
|
401
|
+
| Report output | More complete data | Low |
|
|
402
|
+
| Existing detections | Unchanged | None |
|
|
403
|
+
|
|
404
|
+
### Benefits
|
|
405
|
+
|
|
406
|
+
- ✅ Detects repository injection patterns
|
|
407
|
+
- ✅ Works without call graph dependency
|
|
408
|
+
- ✅ Catches direct repository usage in services
|
|
409
|
+
- ✅ More accurate field detection
|
|
410
|
+
- ✅ Detects query builder patterns
|
|
411
|
+
|
|
412
|
+
### Testing Required
|
|
413
|
+
|
|
414
|
+
- [ ] Test with `@InjectRepository` decorator
|
|
415
|
+
- [ ] Test with `this.repository.method()` patterns
|
|
416
|
+
- [ ] Test with query builder chains
|
|
417
|
+
- [ ] Test with multiple injected repositories
|
|
418
|
+
- [ ] Verify existing call graph detection still works
|
|
419
|
+
- [ ] Integration test with real service files
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Implementation Checklist
|
|
424
|
+
|
|
425
|
+
- [x] Add `detectInjectedRepositories()` method
|
|
426
|
+
- [x] Add `detectOperationFromRepositoryUsage()` method
|
|
427
|
+
- [x] Add `extractFieldsFromQueryBuilder()` method
|
|
428
|
+
- [x] Enhance `analyzeChangedCodeForDatabaseOps()` to use new methods
|
|
429
|
+
- [x] Test with `@InjectRepository` pattern
|
|
430
|
+
- [x] Test with `this.repo.find()` pattern
|
|
431
|
+
- [x] Test with query builder `.select()`, `.where()`
|
|
432
|
+
- [x] Test with multiple repositories
|
|
433
|
+
- [ ] Update tests to cover new patterns
|
|
434
|
+
- [x] Verify backward compatibility
|
|
435
|
+
- [ ] Update documentation
|
|
436
|
+
- [x] Mark bug as RESOLVED
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## References
|
|
441
|
+
|
|
442
|
+
**Code Files:**
|
|
443
|
+
- `core/detectors/database-detector.js` (line ~263-289, ~290-380)
|
|
444
|
+
- `core/report-generator.js` (displays operations)
|
|
445
|
+
|
|
446
|
+
**Related:**
|
|
447
|
+
- BUG-001: `.specify/bugs/bug-001-database-detector.md`
|
|
448
|
+
- Task 002: `.specify/tasks/task-002-database-detector.md`
|
|
449
|
+
- Spec: `.specify/specs/features/database-impact-detection.md`
|
|
450
|
+
- Architecture Doc: `.specify/plans/architecture.md`
|
|
451
|
+
- Code Rules: `.github/copilot-instructions.md`
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
**NestJS/TypeORM Documentation:**
|
|
455
|
+
- Dependency Injection: https://docs.nestjs.com/providers
|
|
456
|
+
- @InjectRepository: https://docs.nestjs.com/techniques/database#repository-pattern
|
|
457
|
+
- Query Builder: https://typeorm.io/select-query-builder
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## Notes
|
|
462
|
+
|
|
463
|
+
### Why Call Graph Approach Fails
|
|
464
|
+
|
|
465
|
+
1. **DI Pattern:** Repositories are injected, not called as methods
|
|
466
|
+
2. **Instance Methods:** `this.repo.method()` doesn't appear in call graph as class method
|
|
467
|
+
3. **Query Builder:** Chained methods create no clear method signature
|
|
468
|
+
4. **Service Layer:** Most business logic uses injected repos, not repository classes directly
|
|
469
|
+
|
|
470
|
+
### Better Approach
|
|
471
|
+
|
|
472
|
+
**Direct pattern matching** is more reliable than call graph for DI patterns:
|
|
473
|
+
- Detect `@InjectRepository(Entity)` → know which table
|
|
474
|
+
- Track field name → know which variable to watch
|
|
475
|
+
- Match `this.fieldName.operation()` → detect usage
|
|
476
|
+
- Parse query builder → extract fields and conditions
|
|
477
|
+
|
|
478
|
+
This approach is **more accurate** and **doesn't depend on AST call graph**.
|