driftdetect-core 0.4.1 → 0.4.2
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/boundaries/boundary-scanner.d.ts +76 -0
- package/dist/boundaries/boundary-scanner.d.ts.map +1 -0
- package/dist/boundaries/boundary-scanner.js +801 -0
- package/dist/boundaries/boundary-scanner.js.map +1 -0
- package/dist/boundaries/data-access-learner.d.ts +126 -0
- package/dist/boundaries/data-access-learner.d.ts.map +1 -0
- package/dist/boundaries/data-access-learner.js +486 -0
- package/dist/boundaries/data-access-learner.js.map +1 -0
- package/dist/boundaries/index.d.ts +6 -0
- package/dist/boundaries/index.d.ts.map +1 -1
- package/dist/boundaries/index.js +6 -0
- package/dist/boundaries/index.js.map +1 -1
- package/dist/boundaries/security-prioritizer.d.ts +118 -0
- package/dist/boundaries/security-prioritizer.d.ts.map +1 -0
- package/dist/boundaries/security-prioritizer.js +316 -0
- package/dist/boundaries/security-prioritizer.js.map +1 -0
- package/dist/call-graph/analysis/coverage-analyzer.d.ts +201 -0
- package/dist/call-graph/analysis/coverage-analyzer.d.ts.map +1 -0
- package/dist/call-graph/analysis/coverage-analyzer.js +553 -0
- package/dist/call-graph/analysis/coverage-analyzer.js.map +1 -0
- package/dist/call-graph/analysis/dead-code-detector.d.ts +145 -0
- package/dist/call-graph/analysis/dead-code-detector.d.ts.map +1 -0
- package/dist/call-graph/analysis/dead-code-detector.js +391 -0
- package/dist/call-graph/analysis/dead-code-detector.js.map +1 -0
- package/dist/call-graph/analysis/graph-builder.d.ts +142 -0
- package/dist/call-graph/analysis/graph-builder.d.ts.map +1 -0
- package/dist/call-graph/analysis/graph-builder.js +624 -0
- package/dist/call-graph/analysis/graph-builder.js.map +1 -0
- package/dist/call-graph/analysis/impact-analyzer.d.ts +150 -0
- package/dist/call-graph/analysis/impact-analyzer.d.ts.map +1 -0
- package/dist/call-graph/analysis/impact-analyzer.js +329 -0
- package/dist/call-graph/analysis/impact-analyzer.js.map +1 -0
- package/dist/call-graph/analysis/index.d.ts +11 -0
- package/dist/call-graph/analysis/index.d.ts.map +1 -0
- package/dist/call-graph/analysis/index.js +9 -0
- package/dist/call-graph/analysis/index.js.map +1 -0
- package/dist/call-graph/analysis/path-finder.d.ts +117 -0
- package/dist/call-graph/analysis/path-finder.d.ts.map +1 -0
- package/dist/call-graph/analysis/path-finder.js +360 -0
- package/dist/call-graph/analysis/path-finder.js.map +1 -0
- package/dist/call-graph/analysis/reachability.d.ts +56 -0
- package/dist/call-graph/analysis/reachability.d.ts.map +1 -0
- package/dist/call-graph/analysis/reachability.js +357 -0
- package/dist/call-graph/analysis/reachability.js.map +1 -0
- package/dist/call-graph/demo.d.ts +11 -0
- package/dist/call-graph/demo.d.ts.map +1 -0
- package/dist/call-graph/demo.js +339 -0
- package/dist/call-graph/demo.js.map +1 -0
- package/dist/call-graph/enrichment/enrichment-engine.d.ts +126 -0
- package/dist/call-graph/enrichment/enrichment-engine.d.ts.map +1 -0
- package/dist/call-graph/enrichment/enrichment-engine.js +760 -0
- package/dist/call-graph/enrichment/enrichment-engine.js.map +1 -0
- package/dist/call-graph/enrichment/impact-scorer.d.ts +59 -0
- package/dist/call-graph/enrichment/impact-scorer.d.ts.map +1 -0
- package/dist/call-graph/enrichment/impact-scorer.js +328 -0
- package/dist/call-graph/enrichment/impact-scorer.js.map +1 -0
- package/dist/call-graph/enrichment/index.d.ts +12 -0
- package/dist/call-graph/enrichment/index.d.ts.map +1 -0
- package/dist/call-graph/enrichment/index.js +15 -0
- package/dist/call-graph/enrichment/index.js.map +1 -0
- package/dist/call-graph/enrichment/remediation-generator.d.ts +41 -0
- package/dist/call-graph/enrichment/remediation-generator.d.ts.map +1 -0
- package/dist/call-graph/enrichment/remediation-generator.js +609 -0
- package/dist/call-graph/enrichment/remediation-generator.js.map +1 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.d.ts +71 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.d.ts.map +1 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.js +454 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.js.map +1 -0
- package/dist/call-graph/enrichment/types.d.ts +402 -0
- package/dist/call-graph/enrichment/types.d.ts.map +1 -0
- package/dist/call-graph/enrichment/types.js +9 -0
- package/dist/call-graph/enrichment/types.js.map +1 -0
- package/dist/call-graph/extractors/base-extractor.d.ts +112 -0
- package/dist/call-graph/extractors/base-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/base-extractor.js +140 -0
- package/dist/call-graph/extractors/base-extractor.js.map +1 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts +76 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.js +387 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/csharp-extractor.d.ts +87 -0
- package/dist/call-graph/extractors/csharp-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/csharp-extractor.js +470 -0
- package/dist/call-graph/extractors/csharp-extractor.js.map +1 -0
- package/dist/call-graph/extractors/data-access-extractor.d.ts +76 -0
- package/dist/call-graph/extractors/data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/data-access-extractor.js +234 -0
- package/dist/call-graph/extractors/data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/index.d.ts +26 -0
- package/dist/call-graph/extractors/index.d.ts.map +1 -0
- package/dist/call-graph/extractors/index.js +36 -0
- package/dist/call-graph/extractors/index.js.map +1 -0
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts +101 -0
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/java-data-access-extractor.js +611 -0
- package/dist/call-graph/extractors/java-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/java-extractor.d.ts +87 -0
- package/dist/call-graph/extractors/java-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/java-extractor.js +510 -0
- package/dist/call-graph/extractors/java-extractor.js.map +1 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts +93 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/php-data-access-extractor.js +589 -0
- package/dist/call-graph/extractors/php-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/php-extractor.d.ts +104 -0
- package/dist/call-graph/extractors/php-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/php-extractor.js +619 -0
- package/dist/call-graph/extractors/php-extractor.js.map +1 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts +90 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/python-data-access-extractor.js +537 -0
- package/dist/call-graph/extractors/python-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/python-extractor.d.ts +98 -0
- package/dist/call-graph/extractors/python-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/python-extractor.js +681 -0
- package/dist/call-graph/extractors/python-extractor.js.map +1 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts +91 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts.map +1 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.js +498 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.js.map +1 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts +122 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.js +788 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/typescript-extractor.d.ts +145 -0
- package/dist/call-graph/extractors/typescript-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/typescript-extractor.js +904 -0
- package/dist/call-graph/extractors/typescript-extractor.js.map +1 -0
- package/dist/call-graph/index.d.ts +127 -0
- package/dist/call-graph/index.d.ts.map +1 -0
- package/dist/call-graph/index.js +247 -0
- package/dist/call-graph/index.js.map +1 -0
- package/dist/call-graph/store/call-graph-store.d.ts +70 -0
- package/dist/call-graph/store/call-graph-store.d.ts.map +1 -0
- package/dist/call-graph/store/call-graph-store.js +210 -0
- package/dist/call-graph/store/call-graph-store.js.map +1 -0
- package/dist/call-graph/store/index.d.ts +7 -0
- package/dist/call-graph/store/index.d.ts.map +1 -0
- package/dist/call-graph/store/index.js +7 -0
- package/dist/call-graph/store/index.js.map +1 -0
- package/dist/call-graph/types.d.ts +376 -0
- package/dist/call-graph/types.d.ts.map +1 -0
- package/dist/call-graph/types.js +8 -0
- package/dist/call-graph/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lake/callgraph-shard-store.d.ts +168 -0
- package/dist/lake/callgraph-shard-store.d.ts.map +1 -0
- package/dist/lake/callgraph-shard-store.js +466 -0
- package/dist/lake/callgraph-shard-store.js.map +1 -0
- package/dist/lake/examples-store.d.ts +127 -0
- package/dist/lake/examples-store.d.ts.map +1 -0
- package/dist/lake/examples-store.js +389 -0
- package/dist/lake/examples-store.js.map +1 -0
- package/dist/lake/index-store.d.ts +82 -0
- package/dist/lake/index-store.d.ts.map +1 -0
- package/dist/lake/index-store.js +359 -0
- package/dist/lake/index-store.js.map +1 -0
- package/dist/lake/index.d.ts +93 -0
- package/dist/lake/index.d.ts.map +1 -0
- package/dist/lake/index.js +138 -0
- package/dist/lake/index.js.map +1 -0
- package/dist/lake/lake.bak/index-store.d.ts +82 -0
- package/dist/lake/lake.bak/index-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/index-store.js +357 -0
- package/dist/lake/lake.bak/index-store.js.map +1 -0
- package/dist/lake/lake.bak/index.d.ts +81 -0
- package/dist/lake/lake.bak/index.d.ts.map +1 -0
- package/dist/lake/lake.bak/index.js +114 -0
- package/dist/lake/lake.bak/index.js.map +1 -0
- package/dist/lake/lake.bak/manifest-store.d.ts +51 -0
- package/dist/lake/lake.bak/manifest-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/manifest-store.js +347 -0
- package/dist/lake/lake.bak/manifest-store.js.map +1 -0
- package/dist/lake/lake.bak/query-engine.d.ts +112 -0
- package/dist/lake/lake.bak/query-engine.d.ts.map +1 -0
- package/dist/lake/lake.bak/query-engine.js +370 -0
- package/dist/lake/lake.bak/query-engine.js.map +1 -0
- package/dist/lake/lake.bak/types.d.ts +428 -0
- package/dist/lake/lake.bak/types.d.ts.map +1 -0
- package/dist/lake/lake.bak/types.js +46 -0
- package/dist/lake/lake.bak/types.js.map +1 -0
- package/dist/lake/lake.bak/view-materializer.d.ts +70 -0
- package/dist/lake/lake.bak/view-materializer.d.ts.map +1 -0
- package/dist/lake/lake.bak/view-materializer.js +314 -0
- package/dist/lake/lake.bak/view-materializer.js.map +1 -0
- package/dist/lake/lake.bak/view-store.d.ts +57 -0
- package/dist/lake/lake.bak/view-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/view-store.js +348 -0
- package/dist/lake/lake.bak/view-store.js.map +1 -0
- package/dist/lake/manifest-store.d.ts +51 -0
- package/dist/lake/manifest-store.d.ts.map +1 -0
- package/dist/lake/manifest-store.js +348 -0
- package/dist/lake/manifest-store.js.map +1 -0
- package/dist/lake/pattern-shard-store.d.ts +87 -0
- package/dist/lake/pattern-shard-store.d.ts.map +1 -0
- package/dist/lake/pattern-shard-store.js +347 -0
- package/dist/lake/pattern-shard-store.js.map +1 -0
- package/dist/lake/query-engine.d.ts +124 -0
- package/dist/lake/query-engine.d.ts.map +1 -0
- package/dist/lake/query-engine.js +453 -0
- package/dist/lake/query-engine.js.map +1 -0
- package/dist/lake/security-shard-store.d.ts +156 -0
- package/dist/lake/security-shard-store.d.ts.map +1 -0
- package/dist/lake/security-shard-store.js +498 -0
- package/dist/lake/security-shard-store.js.map +1 -0
- package/dist/lake/types.d.ts +428 -0
- package/dist/lake/types.d.ts.map +1 -0
- package/dist/lake/types.js +46 -0
- package/dist/lake/types.js.map +1 -0
- package/dist/lake/view-materializer.d.ts +70 -0
- package/dist/lake/view-materializer.d.ts.map +1 -0
- package/dist/lake/view-materializer.js +314 -0
- package/dist/lake/view-materializer.js.map +1 -0
- package/dist/lake/view-store.d.ts +57 -0
- package/dist/lake/view-store.d.ts.map +1 -0
- package/dist/lake/view-store.js +348 -0
- package/dist/lake/view-store.js.map +1 -0
- package/dist/parsers/tree-sitter/index.d.ts +1 -0
- package/dist/parsers/tree-sitter/index.d.ts.map +1 -1
- package/dist/parsers/tree-sitter/index.js +4 -0
- package/dist/parsers/tree-sitter/index.js.map +1 -1
- package/dist/parsers/tree-sitter/typescript-loader.d.ts +58 -0
- package/dist/parsers/tree-sitter/typescript-loader.d.ts.map +1 -0
- package/dist/parsers/tree-sitter/typescript-loader.js +250 -0
- package/dist/parsers/tree-sitter/typescript-loader.js.map +1 -0
- package/dist/store/project-config.d.ts +154 -0
- package/dist/store/project-config.d.ts.map +1 -0
- package/dist/store/project-config.js +235 -0
- package/dist/store/project-config.js.map +1 -0
- package/dist/store/project-registry.d.ts +241 -0
- package/dist/store/project-registry.d.ts.map +1 -0
- package/dist/store/project-registry.js +557 -0
- package/dist/store/project-registry.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript/JavaScript Semantic Data Access Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts data access points from TypeScript/JavaScript using tree-sitter.
|
|
5
|
+
* Provides accurate, semantic-aware detection of database operations.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Supabase: supabase.from('table').select()
|
|
9
|
+
* - Prisma: prisma.user.findMany()
|
|
10
|
+
* - TypeORM: userRepository.find()
|
|
11
|
+
* - Sequelize: User.findAll()
|
|
12
|
+
* - Drizzle: db.select().from(users)
|
|
13
|
+
* - Knex: knex('users').select()
|
|
14
|
+
* - Mongoose: User.find({})
|
|
15
|
+
* - Raw SQL: db.query('SELECT * FROM users')
|
|
16
|
+
*/
|
|
17
|
+
import { BaseDataAccessExtractor } from './data-access-extractor.js';
|
|
18
|
+
import { isTypeScriptTreeSitterAvailable, isJavaScriptTreeSitterAvailable, createParserForFile, } from '../../parsers/tree-sitter/typescript-loader.js';
|
|
19
|
+
/**
|
|
20
|
+
* TypeScript/JavaScript data access extractor using tree-sitter
|
|
21
|
+
*/
|
|
22
|
+
export class TypeScriptDataAccessExtractor extends BaseDataAccessExtractor {
|
|
23
|
+
language = 'typescript';
|
|
24
|
+
extensions = ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'];
|
|
25
|
+
parserCache = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* Check if tree-sitter is available for TypeScript/JavaScript
|
|
28
|
+
*/
|
|
29
|
+
static isAvailable() {
|
|
30
|
+
return isTypeScriptTreeSitterAvailable() || isJavaScriptTreeSitterAvailable();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract data access points from TypeScript/JavaScript source
|
|
34
|
+
*/
|
|
35
|
+
extract(source, filePath) {
|
|
36
|
+
const result = this.createEmptyResult(filePath);
|
|
37
|
+
result.language = this.getLanguageFromPath(filePath);
|
|
38
|
+
const parser = this.getParser(filePath);
|
|
39
|
+
if (!parser) {
|
|
40
|
+
result.errors.push('Tree-sitter not available for TypeScript/JavaScript parsing');
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const tree = parser.parse(source);
|
|
45
|
+
this.visitNode(tree.rootNode, result, filePath, source);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
result.errors.push(error instanceof Error ? error.message : 'Unknown parse error');
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
getLanguageFromPath(filePath) {
|
|
53
|
+
if (filePath.endsWith('.ts') || filePath.endsWith('.tsx') ||
|
|
54
|
+
filePath.endsWith('.mts') || filePath.endsWith('.cts')) {
|
|
55
|
+
return 'typescript';
|
|
56
|
+
}
|
|
57
|
+
return 'javascript';
|
|
58
|
+
}
|
|
59
|
+
getParser(filePath) {
|
|
60
|
+
// Determine parser type based on extension
|
|
61
|
+
const ext = this.getParserExtension(filePath);
|
|
62
|
+
if (!this.parserCache.has(ext)) {
|
|
63
|
+
const parser = createParserForFile(filePath);
|
|
64
|
+
if (parser) {
|
|
65
|
+
this.parserCache.set(ext, parser);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return this.parserCache.get(ext) ?? null;
|
|
69
|
+
}
|
|
70
|
+
getParserExtension(filePath) {
|
|
71
|
+
const lower = filePath.toLowerCase();
|
|
72
|
+
if (lower.endsWith('.tsx'))
|
|
73
|
+
return '.tsx';
|
|
74
|
+
if (lower.endsWith('.ts') || lower.endsWith('.mts') || lower.endsWith('.cts'))
|
|
75
|
+
return '.ts';
|
|
76
|
+
if (lower.endsWith('.jsx'))
|
|
77
|
+
return '.jsx';
|
|
78
|
+
return '.js';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Visit AST nodes to find data access patterns
|
|
82
|
+
*/
|
|
83
|
+
visitNode(node, result, filePath, source) {
|
|
84
|
+
// Check for call expressions
|
|
85
|
+
if (node.type === 'call_expression') {
|
|
86
|
+
this.analyzeCallExpression(node, result, filePath, source);
|
|
87
|
+
}
|
|
88
|
+
// Check for tagged template expressions (SQL template tags)
|
|
89
|
+
if (node.type === 'call_expression' && this.isTaggedTemplate(node)) {
|
|
90
|
+
this.analyzeTaggedTemplate(node, result, filePath, source);
|
|
91
|
+
}
|
|
92
|
+
// Recurse into children
|
|
93
|
+
for (const child of node.children) {
|
|
94
|
+
this.visitNode(child, result, filePath, source);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
isTaggedTemplate(node) {
|
|
98
|
+
// Check if this is a tagged template literal
|
|
99
|
+
return node.children.some(c => c.type === 'template_string');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Analyze a call expression for data access patterns
|
|
103
|
+
*/
|
|
104
|
+
analyzeCallExpression(node, result, filePath, _source) {
|
|
105
|
+
// Get the full call chain
|
|
106
|
+
const chain = this.getCallChain(node);
|
|
107
|
+
// Try each pattern
|
|
108
|
+
const accessPoint = this.trySupabasePattern(chain, node, filePath) ||
|
|
109
|
+
this.tryPrismaPattern(chain, node, filePath) ||
|
|
110
|
+
this.tryTypeORMPattern(chain, node, filePath) ||
|
|
111
|
+
this.trySequelizePattern(chain, node, filePath) ||
|
|
112
|
+
this.tryDrizzlePattern(chain, node, filePath) ||
|
|
113
|
+
this.tryKnexPattern(chain, node, filePath) ||
|
|
114
|
+
this.tryMongoosePattern(chain, node, filePath) ||
|
|
115
|
+
this.tryRawSQLPattern(chain, node, filePath);
|
|
116
|
+
if (accessPoint) {
|
|
117
|
+
// Avoid duplicates
|
|
118
|
+
const exists = result.accessPoints.some(ap => ap.id === accessPoint.id);
|
|
119
|
+
if (!exists) {
|
|
120
|
+
result.accessPoints.push(accessPoint);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the call chain from a call expression
|
|
126
|
+
* e.g., supabase.from('users').select('*') ->
|
|
127
|
+
* names: ['supabase', 'from', 'select']
|
|
128
|
+
* args: [[], ['users'], ['*']]
|
|
129
|
+
*/
|
|
130
|
+
getCallChain(node) {
|
|
131
|
+
const names = [];
|
|
132
|
+
const args = [];
|
|
133
|
+
let current = node;
|
|
134
|
+
while (current) {
|
|
135
|
+
if (current.type === 'call_expression') {
|
|
136
|
+
// Get arguments
|
|
137
|
+
const argsNode = current.childForFieldName('arguments');
|
|
138
|
+
if (argsNode) {
|
|
139
|
+
const argList = [];
|
|
140
|
+
for (const child of argsNode.children) {
|
|
141
|
+
if (child.type !== '(' && child.type !== ')' && child.type !== ',') {
|
|
142
|
+
argList.push(child);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
args.unshift(argList);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
args.unshift([]);
|
|
149
|
+
}
|
|
150
|
+
const funcNode = current.childForFieldName('function');
|
|
151
|
+
if (!funcNode)
|
|
152
|
+
break;
|
|
153
|
+
if (funcNode.type === 'member_expression') {
|
|
154
|
+
const propNode = funcNode.childForFieldName('property');
|
|
155
|
+
if (propNode) {
|
|
156
|
+
names.unshift(propNode.text);
|
|
157
|
+
}
|
|
158
|
+
const objNode = funcNode.childForFieldName('object');
|
|
159
|
+
current = objNode;
|
|
160
|
+
}
|
|
161
|
+
else if (funcNode.type === 'identifier') {
|
|
162
|
+
names.unshift(funcNode.text);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (current.type === 'member_expression') {
|
|
170
|
+
const propNode = current.childForFieldName('property');
|
|
171
|
+
if (propNode) {
|
|
172
|
+
names.unshift(propNode.text);
|
|
173
|
+
}
|
|
174
|
+
args.unshift([]); // No args for property access
|
|
175
|
+
const objNode = current.childForFieldName('object');
|
|
176
|
+
current = objNode;
|
|
177
|
+
}
|
|
178
|
+
else if (current.type === 'identifier') {
|
|
179
|
+
names.unshift(current.text);
|
|
180
|
+
args.unshift([]); // No args for identifier
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { names, args };
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Try to match Supabase pattern: supabase.from('table').select()
|
|
191
|
+
*/
|
|
192
|
+
trySupabasePattern(chain, node, filePath) {
|
|
193
|
+
const fromIndex = chain.names.indexOf('from');
|
|
194
|
+
if (fromIndex === -1)
|
|
195
|
+
return null;
|
|
196
|
+
// Get table name from .from('table')
|
|
197
|
+
const fromArgs = chain.args[fromIndex];
|
|
198
|
+
if (!fromArgs || fromArgs.length === 0)
|
|
199
|
+
return null;
|
|
200
|
+
const tableArg = fromArgs[0];
|
|
201
|
+
if (!tableArg)
|
|
202
|
+
return null;
|
|
203
|
+
const table = this.extractStringValue(tableArg);
|
|
204
|
+
if (!table)
|
|
205
|
+
return null;
|
|
206
|
+
// Determine operation from the chain
|
|
207
|
+
let operation = 'read';
|
|
208
|
+
let fields = [];
|
|
209
|
+
const whereFields = [];
|
|
210
|
+
for (let i = fromIndex + 1; i < chain.names.length; i++) {
|
|
211
|
+
const method = chain.names[i];
|
|
212
|
+
const methodArgs = chain.args[i];
|
|
213
|
+
if (method === 'select' && methodArgs && methodArgs.length > 0) {
|
|
214
|
+
const selectArg = methodArgs[0];
|
|
215
|
+
if (selectArg) {
|
|
216
|
+
const selectStr = this.extractStringValue(selectArg);
|
|
217
|
+
if (selectStr) {
|
|
218
|
+
fields = this.extractFieldsFromString(selectStr);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
operation = 'read';
|
|
222
|
+
}
|
|
223
|
+
else if (method === 'insert' || method === 'upsert') {
|
|
224
|
+
operation = 'write';
|
|
225
|
+
if (methodArgs && methodArgs.length > 0 && methodArgs[0]) {
|
|
226
|
+
fields = this.extractFieldsFromObject(methodArgs[0]);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else if (method === 'update') {
|
|
230
|
+
operation = 'write';
|
|
231
|
+
if (methodArgs && methodArgs.length > 0 && methodArgs[0]) {
|
|
232
|
+
fields = this.extractFieldsFromObject(methodArgs[0]);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else if (method === 'delete') {
|
|
236
|
+
operation = 'delete';
|
|
237
|
+
}
|
|
238
|
+
// Extract fields from where clause methods
|
|
239
|
+
else if (method && this.isWhereClauseMethod(method) && methodArgs && methodArgs.length > 0) {
|
|
240
|
+
const fieldName = this.extractWhereClauseField(methodArgs);
|
|
241
|
+
if (fieldName) {
|
|
242
|
+
whereFields.push(fieldName);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Merge select fields with where clause fields (deduplicated)
|
|
247
|
+
const allFields = [...new Set([...fields, ...whereFields])];
|
|
248
|
+
return this.createAccessPoint({
|
|
249
|
+
table,
|
|
250
|
+
fields: allFields,
|
|
251
|
+
operation,
|
|
252
|
+
file: filePath,
|
|
253
|
+
line: node.startPosition.row + 1,
|
|
254
|
+
column: node.startPosition.column,
|
|
255
|
+
context: node.text.slice(0, 200),
|
|
256
|
+
confidence: 0.95,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if a method name is a where clause method
|
|
261
|
+
*/
|
|
262
|
+
isWhereClauseMethod(method) {
|
|
263
|
+
const whereClauseMethods = [
|
|
264
|
+
// Supabase/PostgREST
|
|
265
|
+
'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'like', 'ilike', 'is', 'in',
|
|
266
|
+
'contains', 'containedBy', 'rangeGt', 'rangeGte', 'rangeLt', 'rangeLte',
|
|
267
|
+
'rangeAdjacent', 'overlaps', 'textSearch', 'match', 'not', 'or', 'filter',
|
|
268
|
+
// Prisma
|
|
269
|
+
'where', 'findFirst', 'findUnique',
|
|
270
|
+
// TypeORM/Sequelize
|
|
271
|
+
'where', 'andWhere', 'orWhere', 'whereIn', 'whereNotIn',
|
|
272
|
+
// Knex
|
|
273
|
+
'where', 'whereNot', 'whereIn', 'whereNotIn', 'whereNull', 'whereNotNull',
|
|
274
|
+
'whereBetween', 'whereNotBetween', 'whereRaw', 'whereExists',
|
|
275
|
+
// Mongoose
|
|
276
|
+
'where', 'equals', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'regex',
|
|
277
|
+
];
|
|
278
|
+
return whereClauseMethods.includes(method);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Extract field name from a where clause method call
|
|
282
|
+
*/
|
|
283
|
+
extractWhereClauseField(args) {
|
|
284
|
+
if (args.length === 0)
|
|
285
|
+
return null;
|
|
286
|
+
// For methods like .eq('field', value), .where('field', value)
|
|
287
|
+
// The first argument is typically the field name
|
|
288
|
+
const firstArg = args[0];
|
|
289
|
+
if (!firstArg)
|
|
290
|
+
return null;
|
|
291
|
+
// Direct string argument: .eq('email', value)
|
|
292
|
+
const strValue = this.extractStringValue(firstArg);
|
|
293
|
+
if (strValue) {
|
|
294
|
+
return strValue;
|
|
295
|
+
}
|
|
296
|
+
// Object argument: .where({ email: value })
|
|
297
|
+
if (firstArg.type === 'object') {
|
|
298
|
+
const objectFields = this.extractFieldsFromObject(firstArg);
|
|
299
|
+
return objectFields[0] ?? null;
|
|
300
|
+
}
|
|
301
|
+
// Identifier: .where(email, value) - less common but possible
|
|
302
|
+
if (firstArg.type === 'identifier') {
|
|
303
|
+
return firstArg.text;
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Try to match Prisma pattern: prisma.user.findMany()
|
|
309
|
+
*/
|
|
310
|
+
tryPrismaPattern(chain, node, filePath) {
|
|
311
|
+
if (chain.names.length < 3)
|
|
312
|
+
return null;
|
|
313
|
+
const firstPart = chain.names[0]?.toLowerCase();
|
|
314
|
+
if (!firstPart?.includes('prisma'))
|
|
315
|
+
return null;
|
|
316
|
+
const modelName = chain.names[1];
|
|
317
|
+
const methodName = chain.names[2];
|
|
318
|
+
if (!modelName || !methodName)
|
|
319
|
+
return null;
|
|
320
|
+
// Skip if model name looks like a method
|
|
321
|
+
const prismaClientMethods = ['$connect', '$disconnect', '$transaction', '$queryRaw', '$executeRaw'];
|
|
322
|
+
if (prismaClientMethods.includes(modelName))
|
|
323
|
+
return null;
|
|
324
|
+
const operation = this.detectOperation(methodName);
|
|
325
|
+
if (!operation)
|
|
326
|
+
return null;
|
|
327
|
+
// Extract fields from select/include
|
|
328
|
+
let fields = [];
|
|
329
|
+
const methodArgs = chain.args[2];
|
|
330
|
+
if (methodArgs && methodArgs.length > 0 && methodArgs[0]) {
|
|
331
|
+
fields = this.extractPrismaFields(methodArgs[0]);
|
|
332
|
+
}
|
|
333
|
+
return this.createAccessPoint({
|
|
334
|
+
table: this.inferTableFromName(modelName),
|
|
335
|
+
fields,
|
|
336
|
+
operation,
|
|
337
|
+
file: filePath,
|
|
338
|
+
line: node.startPosition.row + 1,
|
|
339
|
+
column: node.startPosition.column,
|
|
340
|
+
context: node.text.slice(0, 200),
|
|
341
|
+
confidence: 0.95,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Try to match TypeORM pattern: repository.find() or Entity.find()
|
|
346
|
+
*/
|
|
347
|
+
tryTypeORMPattern(chain, node, filePath) {
|
|
348
|
+
if (chain.names.length < 2)
|
|
349
|
+
return null;
|
|
350
|
+
const receiverName = chain.names[0];
|
|
351
|
+
const methodName = chain.names[1];
|
|
352
|
+
if (!receiverName || !methodName)
|
|
353
|
+
return null;
|
|
354
|
+
const isRepository = receiverName.toLowerCase().includes('repository') ||
|
|
355
|
+
receiverName.toLowerCase().includes('repo');
|
|
356
|
+
const isEntity = /^[A-Z]/.test(receiverName) &&
|
|
357
|
+
!['Array', 'Object', 'String', 'Number', 'Boolean', 'Promise', 'Map', 'Set'].includes(receiverName);
|
|
358
|
+
if (!isRepository && !isEntity)
|
|
359
|
+
return null;
|
|
360
|
+
const typeormMethods = ['find', 'findOne', 'findOneBy', 'findBy', 'findAndCount', 'save', 'insert', 'update', 'delete', 'remove', 'count'];
|
|
361
|
+
if (!typeormMethods.includes(methodName))
|
|
362
|
+
return null;
|
|
363
|
+
const operation = this.detectOperation(methodName);
|
|
364
|
+
if (!operation)
|
|
365
|
+
return null;
|
|
366
|
+
return this.createAccessPoint({
|
|
367
|
+
table: this.inferTableFromName(receiverName),
|
|
368
|
+
fields: [],
|
|
369
|
+
operation,
|
|
370
|
+
file: filePath,
|
|
371
|
+
line: node.startPosition.row + 1,
|
|
372
|
+
column: node.startPosition.column,
|
|
373
|
+
context: node.text.slice(0, 200),
|
|
374
|
+
confidence: 0.85,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Try to match Sequelize pattern: Model.findAll()
|
|
379
|
+
*/
|
|
380
|
+
trySequelizePattern(chain, node, filePath) {
|
|
381
|
+
if (chain.names.length < 2)
|
|
382
|
+
return null;
|
|
383
|
+
const modelName = chain.names[0];
|
|
384
|
+
const methodName = chain.names[1];
|
|
385
|
+
if (!modelName || !methodName)
|
|
386
|
+
return null;
|
|
387
|
+
if (!/^[A-Z]/.test(modelName))
|
|
388
|
+
return null;
|
|
389
|
+
const sequelizeMethods = ['findAll', 'findOne', 'findByPk', 'findOrCreate', 'create', 'bulkCreate', 'update', 'destroy', 'count'];
|
|
390
|
+
if (!sequelizeMethods.includes(methodName))
|
|
391
|
+
return null;
|
|
392
|
+
const operation = this.detectOperation(methodName);
|
|
393
|
+
if (!operation)
|
|
394
|
+
return null;
|
|
395
|
+
// Extract fields from attributes option
|
|
396
|
+
let fields = [];
|
|
397
|
+
const methodArgs = chain.args[1];
|
|
398
|
+
if (methodArgs && methodArgs.length > 0 && methodArgs[0]) {
|
|
399
|
+
fields = this.extractSequelizeFields(methodArgs[0]);
|
|
400
|
+
}
|
|
401
|
+
return this.createAccessPoint({
|
|
402
|
+
table: this.inferTableFromName(modelName),
|
|
403
|
+
fields,
|
|
404
|
+
operation,
|
|
405
|
+
file: filePath,
|
|
406
|
+
line: node.startPosition.row + 1,
|
|
407
|
+
column: node.startPosition.column,
|
|
408
|
+
context: node.text.slice(0, 200),
|
|
409
|
+
confidence: 0.85,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Try to match Drizzle pattern: db.select().from(table)
|
|
414
|
+
*/
|
|
415
|
+
tryDrizzlePattern(chain, node, filePath) {
|
|
416
|
+
const fromIndex = chain.names.indexOf('from');
|
|
417
|
+
if (fromIndex === -1)
|
|
418
|
+
return null;
|
|
419
|
+
const hasSelect = chain.names.includes('select') || chain.names.includes('selectDistinct');
|
|
420
|
+
const hasInsert = chain.names.includes('insert');
|
|
421
|
+
const hasUpdate = chain.names.includes('update');
|
|
422
|
+
const hasDelete = chain.names.includes('delete');
|
|
423
|
+
if (!hasSelect && !hasInsert && !hasUpdate && !hasDelete)
|
|
424
|
+
return null;
|
|
425
|
+
const fromArgs = chain.args[fromIndex];
|
|
426
|
+
if (!fromArgs || fromArgs.length === 0)
|
|
427
|
+
return null;
|
|
428
|
+
const tableArg = fromArgs[0];
|
|
429
|
+
if (!tableArg)
|
|
430
|
+
return null;
|
|
431
|
+
let table = 'unknown';
|
|
432
|
+
if (tableArg.type === 'identifier') {
|
|
433
|
+
table = this.inferTableFromName(tableArg.text);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
const strVal = this.extractStringValue(tableArg);
|
|
437
|
+
if (strVal)
|
|
438
|
+
table = strVal;
|
|
439
|
+
}
|
|
440
|
+
let operation = 'read';
|
|
441
|
+
if (hasInsert)
|
|
442
|
+
operation = 'write';
|
|
443
|
+
if (hasUpdate)
|
|
444
|
+
operation = 'write';
|
|
445
|
+
if (hasDelete)
|
|
446
|
+
operation = 'delete';
|
|
447
|
+
return this.createAccessPoint({
|
|
448
|
+
table,
|
|
449
|
+
fields: [],
|
|
450
|
+
operation,
|
|
451
|
+
file: filePath,
|
|
452
|
+
line: node.startPosition.row + 1,
|
|
453
|
+
column: node.startPosition.column,
|
|
454
|
+
context: node.text.slice(0, 200),
|
|
455
|
+
confidence: 0.9,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Try to match Knex pattern: knex('table').select()
|
|
460
|
+
*/
|
|
461
|
+
tryKnexPattern(chain, node, filePath) {
|
|
462
|
+
if (chain.names.length < 2)
|
|
463
|
+
return null;
|
|
464
|
+
const firstPart = chain.names[0]?.toLowerCase();
|
|
465
|
+
if (!firstPart?.includes('knex') && !firstPart?.includes('db'))
|
|
466
|
+
return null;
|
|
467
|
+
const firstArgs = chain.args[0];
|
|
468
|
+
if (!firstArgs || firstArgs.length === 0)
|
|
469
|
+
return null;
|
|
470
|
+
const tableArg = firstArgs[0];
|
|
471
|
+
if (!tableArg)
|
|
472
|
+
return null;
|
|
473
|
+
const table = this.extractStringValue(tableArg);
|
|
474
|
+
if (!table)
|
|
475
|
+
return null;
|
|
476
|
+
let operation = 'read';
|
|
477
|
+
for (const method of chain.names) {
|
|
478
|
+
const op = this.detectOperation(method);
|
|
479
|
+
if (op) {
|
|
480
|
+
operation = op;
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return this.createAccessPoint({
|
|
485
|
+
table,
|
|
486
|
+
fields: [],
|
|
487
|
+
operation,
|
|
488
|
+
file: filePath,
|
|
489
|
+
line: node.startPosition.row + 1,
|
|
490
|
+
column: node.startPosition.column,
|
|
491
|
+
context: node.text.slice(0, 200),
|
|
492
|
+
confidence: 0.9,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Try to match Mongoose pattern: User.find({})
|
|
497
|
+
*/
|
|
498
|
+
tryMongoosePattern(chain, node, filePath) {
|
|
499
|
+
if (chain.names.length < 2)
|
|
500
|
+
return null;
|
|
501
|
+
const modelName = chain.names[0];
|
|
502
|
+
const methodName = chain.names[1];
|
|
503
|
+
if (!modelName || !methodName)
|
|
504
|
+
return null;
|
|
505
|
+
if (!/^[A-Z]/.test(modelName))
|
|
506
|
+
return null;
|
|
507
|
+
const mongooseMethods = {
|
|
508
|
+
read: ['find', 'findOne', 'findById', 'findOneAndUpdate', 'findByIdAndUpdate',
|
|
509
|
+
'countDocuments', 'estimatedDocumentCount', 'distinct', 'exists',
|
|
510
|
+
'aggregate', 'populate', 'lean', 'exec'],
|
|
511
|
+
write: ['create', 'insertMany', 'save', 'updateOne', 'updateMany',
|
|
512
|
+
'findOneAndUpdate', 'findByIdAndUpdate', 'replaceOne', 'bulkWrite'],
|
|
513
|
+
delete: ['deleteOne', 'deleteMany', 'findOneAndDelete', 'findByIdAndDelete',
|
|
514
|
+
'findOneAndRemove', 'findByIdAndRemove', 'remove'],
|
|
515
|
+
};
|
|
516
|
+
let operation = null;
|
|
517
|
+
if (mongooseMethods.read.includes(methodName)) {
|
|
518
|
+
operation = 'read';
|
|
519
|
+
}
|
|
520
|
+
else if (mongooseMethods.write.includes(methodName)) {
|
|
521
|
+
operation = 'write';
|
|
522
|
+
}
|
|
523
|
+
else if (mongooseMethods.delete.includes(methodName)) {
|
|
524
|
+
operation = 'delete';
|
|
525
|
+
}
|
|
526
|
+
if (!operation)
|
|
527
|
+
return null;
|
|
528
|
+
// Extract fields from projection
|
|
529
|
+
let fields = [];
|
|
530
|
+
const methodArgs = chain.args[1];
|
|
531
|
+
if (methodArgs && methodArgs.length >= 2) {
|
|
532
|
+
const projectionArg = methodArgs[1];
|
|
533
|
+
if (projectionArg) {
|
|
534
|
+
if (projectionArg.type === 'object') {
|
|
535
|
+
fields = this.extractFieldsFromObject(projectionArg);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
const strVal = this.extractStringValue(projectionArg);
|
|
539
|
+
if (strVal) {
|
|
540
|
+
fields = strVal.split(/\s+/).filter(f => f && !f.startsWith('-'));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return this.createAccessPoint({
|
|
546
|
+
table: this.inferTableFromName(modelName),
|
|
547
|
+
fields,
|
|
548
|
+
operation,
|
|
549
|
+
file: filePath,
|
|
550
|
+
line: node.startPosition.row + 1,
|
|
551
|
+
column: node.startPosition.column,
|
|
552
|
+
context: node.text.slice(0, 200),
|
|
553
|
+
confidence: 0.85,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Try to match raw SQL pattern: db.query('SELECT * FROM users')
|
|
558
|
+
*/
|
|
559
|
+
tryRawSQLPattern(chain, node, filePath) {
|
|
560
|
+
const queryMethods = ['query', 'execute', 'raw', 'rawQuery', '$queryRaw', '$executeRaw', 'sql'];
|
|
561
|
+
const methodIndex = chain.names.findIndex(n => queryMethods.includes(n));
|
|
562
|
+
if (methodIndex === -1)
|
|
563
|
+
return null;
|
|
564
|
+
const methodArgs = chain.args[methodIndex];
|
|
565
|
+
if (!methodArgs || methodArgs.length === 0)
|
|
566
|
+
return null;
|
|
567
|
+
const sqlArg = methodArgs[0];
|
|
568
|
+
if (!sqlArg)
|
|
569
|
+
return null;
|
|
570
|
+
let sqlText = this.extractStringValue(sqlArg);
|
|
571
|
+
if (!sqlText && sqlArg.type === 'template_string') {
|
|
572
|
+
sqlText = sqlArg.text;
|
|
573
|
+
}
|
|
574
|
+
if (!sqlText)
|
|
575
|
+
return null;
|
|
576
|
+
const { table, operation, fields } = this.parseSQLStatement(sqlText);
|
|
577
|
+
if (!table || table === 'unknown')
|
|
578
|
+
return null;
|
|
579
|
+
return this.createAccessPoint({
|
|
580
|
+
table,
|
|
581
|
+
fields,
|
|
582
|
+
operation,
|
|
583
|
+
file: filePath,
|
|
584
|
+
line: node.startPosition.row + 1,
|
|
585
|
+
column: node.startPosition.column,
|
|
586
|
+
context: node.text.slice(0, 200),
|
|
587
|
+
isRawSql: true,
|
|
588
|
+
confidence: 0.8,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Analyze tagged template expressions for SQL
|
|
593
|
+
*/
|
|
594
|
+
analyzeTaggedTemplate(node, result, filePath, _source) {
|
|
595
|
+
// Get the tag name
|
|
596
|
+
const funcNode = node.childForFieldName('function');
|
|
597
|
+
if (!funcNode)
|
|
598
|
+
return;
|
|
599
|
+
let tagName = '';
|
|
600
|
+
if (funcNode.type === 'identifier') {
|
|
601
|
+
tagName = funcNode.text;
|
|
602
|
+
}
|
|
603
|
+
else if (funcNode.type === 'member_expression') {
|
|
604
|
+
const propNode = funcNode.childForFieldName('property');
|
|
605
|
+
if (propNode)
|
|
606
|
+
tagName = propNode.text;
|
|
607
|
+
}
|
|
608
|
+
const sqlTags = ['sql', 'Sql', 'SQL', 'raw', 'query'];
|
|
609
|
+
if (!sqlTags.includes(tagName))
|
|
610
|
+
return;
|
|
611
|
+
// Get the template content
|
|
612
|
+
const templateNode = node.children.find(c => c.type === 'template_string');
|
|
613
|
+
if (!templateNode)
|
|
614
|
+
return;
|
|
615
|
+
const sqlText = templateNode.text;
|
|
616
|
+
if (!sqlText)
|
|
617
|
+
return;
|
|
618
|
+
const { table, operation, fields } = this.parseSQLStatement(sqlText);
|
|
619
|
+
if (!table || table === 'unknown')
|
|
620
|
+
return;
|
|
621
|
+
const accessPoint = this.createAccessPoint({
|
|
622
|
+
table,
|
|
623
|
+
fields,
|
|
624
|
+
operation,
|
|
625
|
+
file: filePath,
|
|
626
|
+
line: node.startPosition.row + 1,
|
|
627
|
+
column: node.startPosition.column,
|
|
628
|
+
context: node.text.slice(0, 200),
|
|
629
|
+
isRawSql: true,
|
|
630
|
+
confidence: 0.8,
|
|
631
|
+
});
|
|
632
|
+
const exists = result.accessPoints.some(ap => ap.id === accessPoint.id);
|
|
633
|
+
if (!exists) {
|
|
634
|
+
result.accessPoints.push(accessPoint);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Parse a SQL statement to extract table, operation, and fields
|
|
639
|
+
*/
|
|
640
|
+
parseSQLStatement(sql) {
|
|
641
|
+
const upperSql = sql.toUpperCase().trim();
|
|
642
|
+
let operation = 'unknown';
|
|
643
|
+
let table = 'unknown';
|
|
644
|
+
const fields = [];
|
|
645
|
+
if (upperSql.startsWith('SELECT')) {
|
|
646
|
+
operation = 'read';
|
|
647
|
+
}
|
|
648
|
+
else if (upperSql.startsWith('INSERT')) {
|
|
649
|
+
operation = 'write';
|
|
650
|
+
}
|
|
651
|
+
else if (upperSql.startsWith('UPDATE')) {
|
|
652
|
+
operation = 'write';
|
|
653
|
+
}
|
|
654
|
+
else if (upperSql.startsWith('DELETE')) {
|
|
655
|
+
operation = 'delete';
|
|
656
|
+
}
|
|
657
|
+
const fromMatch = sql.match(/FROM\s+["'`]?(\w+)["'`]?/i);
|
|
658
|
+
const intoMatch = sql.match(/INTO\s+["'`]?(\w+)["'`]?/i);
|
|
659
|
+
const updateMatch = sql.match(/UPDATE\s+["'`]?(\w+)["'`]?/i);
|
|
660
|
+
if (fromMatch?.[1])
|
|
661
|
+
table = fromMatch[1];
|
|
662
|
+
else if (intoMatch?.[1])
|
|
663
|
+
table = intoMatch[1];
|
|
664
|
+
else if (updateMatch?.[1])
|
|
665
|
+
table = updateMatch[1];
|
|
666
|
+
if (operation === 'read') {
|
|
667
|
+
const selectMatch = sql.match(/SELECT\s+(.+?)\s+FROM/i);
|
|
668
|
+
if (selectMatch?.[1] && selectMatch[1] !== '*') {
|
|
669
|
+
const fieldList = selectMatch[1].split(',').map(f => f.trim());
|
|
670
|
+
for (const field of fieldList) {
|
|
671
|
+
const fieldName = field.split(/\s+as\s+/i)[0]?.trim();
|
|
672
|
+
if (fieldName && !fieldName.includes('(')) {
|
|
673
|
+
fields.push(fieldName.replace(/["'`]/g, ''));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return { table, operation, fields };
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Extract string value from a node
|
|
682
|
+
*/
|
|
683
|
+
extractStringValue(node) {
|
|
684
|
+
if (!node)
|
|
685
|
+
return null;
|
|
686
|
+
if (node.type === 'string') {
|
|
687
|
+
// Remove quotes
|
|
688
|
+
return node.text.replace(/^['"`]|['"`]$/g, '');
|
|
689
|
+
}
|
|
690
|
+
if (node.type === 'template_string') {
|
|
691
|
+
// Remove backticks
|
|
692
|
+
return node.text.replace(/^`|`$/g, '');
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Extract fields from an object literal
|
|
698
|
+
*/
|
|
699
|
+
extractFieldsFromObject(node) {
|
|
700
|
+
if (!node)
|
|
701
|
+
return [];
|
|
702
|
+
const fields = [];
|
|
703
|
+
if (node.type === 'object') {
|
|
704
|
+
for (const child of node.children) {
|
|
705
|
+
if (child.type === 'pair') {
|
|
706
|
+
const keyNode = child.childForFieldName('key');
|
|
707
|
+
if (keyNode) {
|
|
708
|
+
if (keyNode.type === 'property_identifier' || keyNode.type === 'identifier') {
|
|
709
|
+
fields.push(keyNode.text);
|
|
710
|
+
}
|
|
711
|
+
else if (keyNode.type === 'string') {
|
|
712
|
+
fields.push(keyNode.text.replace(/^['"`]|['"`]$/g, ''));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else if (child.type === 'shorthand_property_identifier') {
|
|
717
|
+
fields.push(child.text);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
else if (node.type === 'array') {
|
|
722
|
+
// For array of objects, extract from first element
|
|
723
|
+
const firstElem = node.children.find(c => c.type === 'object');
|
|
724
|
+
if (firstElem) {
|
|
725
|
+
return this.extractFieldsFromObject(firstElem);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return fields;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Extract fields from Prisma select/include options
|
|
732
|
+
*/
|
|
733
|
+
extractPrismaFields(node) {
|
|
734
|
+
if (!node || node.type !== 'object')
|
|
735
|
+
return [];
|
|
736
|
+
const fields = [];
|
|
737
|
+
for (const child of node.children) {
|
|
738
|
+
if (child.type === 'pair') {
|
|
739
|
+
const keyNode = child.childForFieldName('key');
|
|
740
|
+
const valueNode = child.childForFieldName('value');
|
|
741
|
+
if (keyNode && (keyNode.text === 'select' || keyNode.text === 'include')) {
|
|
742
|
+
if (valueNode && valueNode.type === 'object') {
|
|
743
|
+
for (const selectChild of valueNode.children) {
|
|
744
|
+
if (selectChild.type === 'pair') {
|
|
745
|
+
const selectKey = selectChild.childForFieldName('key');
|
|
746
|
+
if (selectKey) {
|
|
747
|
+
fields.push(selectKey.text);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return fields;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Extract fields from Sequelize attributes option
|
|
759
|
+
*/
|
|
760
|
+
extractSequelizeFields(node) {
|
|
761
|
+
if (!node || node.type !== 'object')
|
|
762
|
+
return [];
|
|
763
|
+
const fields = [];
|
|
764
|
+
for (const child of node.children) {
|
|
765
|
+
if (child.type === 'pair') {
|
|
766
|
+
const keyNode = child.childForFieldName('key');
|
|
767
|
+
const valueNode = child.childForFieldName('value');
|
|
768
|
+
if (keyNode && keyNode.text === 'attributes') {
|
|
769
|
+
if (valueNode && valueNode.type === 'array') {
|
|
770
|
+
for (const elem of valueNode.children) {
|
|
771
|
+
if (elem.type === 'string') {
|
|
772
|
+
fields.push(elem.text.replace(/^['"`]|['"`]$/g, ''));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return fields;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Create a TypeScript data access extractor
|
|
784
|
+
*/
|
|
785
|
+
export function createTypeScriptDataAccessExtractor() {
|
|
786
|
+
return new TypeScriptDataAccessExtractor();
|
|
787
|
+
}
|
|
788
|
+
//# sourceMappingURL=typescript-data-access-extractor.js.map
|