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,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHP Semantic Data Access Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts data access points from PHP using tree-sitter.
|
|
5
|
+
* Provides accurate, semantic-aware detection of database operations.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Laravel Eloquent: User::where()->get(), $user->save()
|
|
9
|
+
* - Laravel Query Builder: DB::table('users')->get()
|
|
10
|
+
* - Doctrine ORM: $em->getRepository()->find()
|
|
11
|
+
* - PDO: $pdo->query("SELECT...")
|
|
12
|
+
* - Raw SQL: DB::select("SELECT...")
|
|
13
|
+
*/
|
|
14
|
+
import { BaseDataAccessExtractor, type DataAccessExtractionResult } from './data-access-extractor.js';
|
|
15
|
+
import type { CallGraphLanguage } from '../types.js';
|
|
16
|
+
/**
|
|
17
|
+
* PHP data access extractor using tree-sitter
|
|
18
|
+
*/
|
|
19
|
+
export declare class PhpDataAccessExtractor extends BaseDataAccessExtractor {
|
|
20
|
+
readonly language: CallGraphLanguage;
|
|
21
|
+
readonly extensions: string[];
|
|
22
|
+
private parser;
|
|
23
|
+
/**
|
|
24
|
+
* Check if tree-sitter is available for PHP
|
|
25
|
+
*/
|
|
26
|
+
static isAvailable(): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Extract data access points from PHP source
|
|
29
|
+
*/
|
|
30
|
+
extract(source: string, filePath: string): DataAccessExtractionResult;
|
|
31
|
+
/**
|
|
32
|
+
* Visit AST nodes to find data access patterns
|
|
33
|
+
*/
|
|
34
|
+
private visitNode;
|
|
35
|
+
/**
|
|
36
|
+
* Analyze a member call expression (->method())
|
|
37
|
+
*/
|
|
38
|
+
private analyzeMemberCall;
|
|
39
|
+
/**
|
|
40
|
+
* Analyze a static call expression (Class::method())
|
|
41
|
+
*/
|
|
42
|
+
private analyzeStaticCall;
|
|
43
|
+
/**
|
|
44
|
+
* Get the call chain from a member call expression
|
|
45
|
+
*/
|
|
46
|
+
private getCallChain;
|
|
47
|
+
/**
|
|
48
|
+
* Get the call chain from a static call expression
|
|
49
|
+
*/
|
|
50
|
+
private getStaticCallChain;
|
|
51
|
+
/**
|
|
52
|
+
* Try to match Laravel Eloquent static pattern
|
|
53
|
+
* e.g., User::where('active', true)->get(), User::find(1)
|
|
54
|
+
*/
|
|
55
|
+
private tryEloquentStaticPattern;
|
|
56
|
+
/**
|
|
57
|
+
* Extract field name from a where clause method call
|
|
58
|
+
*/
|
|
59
|
+
private extractWhereClauseField;
|
|
60
|
+
/**
|
|
61
|
+
* Try to match Laravel DB facade pattern
|
|
62
|
+
* e.g., DB::table('users')->get(), DB::select("SELECT...")
|
|
63
|
+
*/
|
|
64
|
+
private tryLaravelDbPattern;
|
|
65
|
+
/**
|
|
66
|
+
* Try to match Eloquent instance pattern
|
|
67
|
+
* e.g., $user->save(), $user->delete()
|
|
68
|
+
*/
|
|
69
|
+
private tryEloquentInstancePattern;
|
|
70
|
+
/**
|
|
71
|
+
* Try to match Doctrine ORM pattern
|
|
72
|
+
* e.g., $em->getRepository(User::class)->find(1)
|
|
73
|
+
*/
|
|
74
|
+
private tryDoctrinePattern;
|
|
75
|
+
/**
|
|
76
|
+
* Try to match PDO pattern
|
|
77
|
+
* e.g., $pdo->query("SELECT..."), $stmt->execute()
|
|
78
|
+
*/
|
|
79
|
+
private tryPdoPattern;
|
|
80
|
+
/**
|
|
81
|
+
* Extract string value from a node
|
|
82
|
+
*/
|
|
83
|
+
private extractStringValue;
|
|
84
|
+
/**
|
|
85
|
+
* Parse a SQL statement to extract table, operation, and fields
|
|
86
|
+
*/
|
|
87
|
+
private parseSQLStatement;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create a PHP data access extractor
|
|
91
|
+
*/
|
|
92
|
+
export declare function createPhpDataAccessExtractor(): PhpDataAccessExtractor;
|
|
93
|
+
//# sourceMappingURL=php-data-access-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"php-data-access-extractor.d.ts","sourceRoot":"","sources":["../../../src/call-graph/extractors/php-data-access-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,uBAAuB,EAAE,KAAK,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AACtG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAQrD;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,uBAAuB;IACjE,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAS;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAY;IAEzC,OAAO,CAAC,MAAM,CAAiC;IAE/C;;OAEG;IACH,MAAM,CAAC,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,0BAA0B;IAwBrE;;OAEG;IACH,OAAO,CAAC,SAAS;IAsBjB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAqBzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoBzB;;OAEG;IACH,OAAO,CAAC,YAAY;IAgDpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA2D1B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA0FhC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAmB/B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAwG3B;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAyClC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAgD1B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAwDrB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAiB1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAqB1B;AAED;;GAEG;AACH,wBAAgB,4BAA4B,IAAI,sBAAsB,CAErE"}
|
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHP Semantic Data Access Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts data access points from PHP using tree-sitter.
|
|
5
|
+
* Provides accurate, semantic-aware detection of database operations.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - Laravel Eloquent: User::where()->get(), $user->save()
|
|
9
|
+
* - Laravel Query Builder: DB::table('users')->get()
|
|
10
|
+
* - Doctrine ORM: $em->getRepository()->find()
|
|
11
|
+
* - PDO: $pdo->query("SELECT...")
|
|
12
|
+
* - Raw SQL: DB::select("SELECT...")
|
|
13
|
+
*/
|
|
14
|
+
import { BaseDataAccessExtractor } from './data-access-extractor.js';
|
|
15
|
+
import { isPhpTreeSitterAvailable, createPhpParser, } from '../../parsers/tree-sitter/php-loader.js';
|
|
16
|
+
/**
|
|
17
|
+
* PHP data access extractor using tree-sitter
|
|
18
|
+
*/
|
|
19
|
+
export class PhpDataAccessExtractor extends BaseDataAccessExtractor {
|
|
20
|
+
language = 'php';
|
|
21
|
+
extensions = ['.php'];
|
|
22
|
+
parser = null;
|
|
23
|
+
/**
|
|
24
|
+
* Check if tree-sitter is available for PHP
|
|
25
|
+
*/
|
|
26
|
+
static isAvailable() {
|
|
27
|
+
return isPhpTreeSitterAvailable();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Extract data access points from PHP source
|
|
31
|
+
*/
|
|
32
|
+
extract(source, filePath) {
|
|
33
|
+
const result = this.createEmptyResult(filePath);
|
|
34
|
+
if (!isPhpTreeSitterAvailable()) {
|
|
35
|
+
result.errors.push('Tree-sitter not available for PHP parsing');
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
if (!this.parser) {
|
|
40
|
+
this.parser = createPhpParser();
|
|
41
|
+
}
|
|
42
|
+
const tree = this.parser.parse(source);
|
|
43
|
+
this.visitNode(tree.rootNode, result, filePath, source);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
result.errors.push(error instanceof Error ? error.message : 'Unknown parse error');
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Visit AST nodes to find data access patterns
|
|
52
|
+
*/
|
|
53
|
+
visitNode(node, result, filePath, source) {
|
|
54
|
+
// Check for method calls (->method())
|
|
55
|
+
if (node.type === 'member_call_expression') {
|
|
56
|
+
this.analyzeMemberCall(node, result, filePath, source);
|
|
57
|
+
}
|
|
58
|
+
// Check for static method calls (Class::method())
|
|
59
|
+
if (node.type === 'scoped_call_expression') {
|
|
60
|
+
this.analyzeStaticCall(node, result, filePath, source);
|
|
61
|
+
}
|
|
62
|
+
// Recurse into children
|
|
63
|
+
for (const child of node.children) {
|
|
64
|
+
this.visitNode(child, result, filePath, source);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Analyze a member call expression (->method())
|
|
69
|
+
*/
|
|
70
|
+
analyzeMemberCall(node, result, filePath, _source) {
|
|
71
|
+
const chain = this.getCallChain(node);
|
|
72
|
+
const accessPoint = this.tryEloquentInstancePattern(chain, node, filePath) ||
|
|
73
|
+
this.tryDoctrinePattern(chain, node, filePath) ||
|
|
74
|
+
this.tryPdoPattern(chain, node, filePath);
|
|
75
|
+
if (accessPoint) {
|
|
76
|
+
const exists = result.accessPoints.some(ap => ap.id === accessPoint.id);
|
|
77
|
+
if (!exists) {
|
|
78
|
+
result.accessPoints.push(accessPoint);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Analyze a static call expression (Class::method())
|
|
84
|
+
*/
|
|
85
|
+
analyzeStaticCall(node, result, filePath, _source) {
|
|
86
|
+
const chain = this.getStaticCallChain(node);
|
|
87
|
+
const accessPoint = this.tryEloquentStaticPattern(chain, node, filePath) ||
|
|
88
|
+
this.tryLaravelDbPattern(chain, node, filePath);
|
|
89
|
+
if (accessPoint) {
|
|
90
|
+
const exists = result.accessPoints.some(ap => ap.id === accessPoint.id);
|
|
91
|
+
if (!exists) {
|
|
92
|
+
result.accessPoints.push(accessPoint);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the call chain from a member call expression
|
|
98
|
+
*/
|
|
99
|
+
getCallChain(node) {
|
|
100
|
+
const names = [];
|
|
101
|
+
const args = [];
|
|
102
|
+
let current = node;
|
|
103
|
+
while (current) {
|
|
104
|
+
if (current.type === 'member_call_expression') {
|
|
105
|
+
const nameNode = current.childForFieldName('name');
|
|
106
|
+
if (nameNode) {
|
|
107
|
+
names.unshift(nameNode.text);
|
|
108
|
+
}
|
|
109
|
+
const argsNode = current.childForFieldName('arguments');
|
|
110
|
+
if (argsNode) {
|
|
111
|
+
const argList = [];
|
|
112
|
+
for (const child of argsNode.children) {
|
|
113
|
+
if (child.type !== '(' && child.type !== ')' && child.type !== ',' && child.type !== 'argument') {
|
|
114
|
+
argList.push(child);
|
|
115
|
+
}
|
|
116
|
+
else if (child.type === 'argument') {
|
|
117
|
+
argList.push(child);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
args.unshift(argList);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
args.unshift([]);
|
|
124
|
+
}
|
|
125
|
+
current = current.childForFieldName('object');
|
|
126
|
+
}
|
|
127
|
+
else if (current.type === 'member_access_expression') {
|
|
128
|
+
const nameNode = current.childForFieldName('name');
|
|
129
|
+
if (nameNode) {
|
|
130
|
+
names.unshift(nameNode.text);
|
|
131
|
+
args.unshift([]);
|
|
132
|
+
}
|
|
133
|
+
current = current.childForFieldName('object');
|
|
134
|
+
}
|
|
135
|
+
else if (current.type === 'variable_name' || current.type === 'name') {
|
|
136
|
+
names.unshift(current.text.replace(/^\$/, ''));
|
|
137
|
+
args.unshift([]);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { names, args };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get the call chain from a static call expression
|
|
148
|
+
*/
|
|
149
|
+
getStaticCallChain(node) {
|
|
150
|
+
const names = [];
|
|
151
|
+
const args = [];
|
|
152
|
+
let className = '';
|
|
153
|
+
// Get the class name
|
|
154
|
+
const scopeNode = node.childForFieldName('scope');
|
|
155
|
+
if (scopeNode) {
|
|
156
|
+
className = scopeNode.text;
|
|
157
|
+
}
|
|
158
|
+
// Get the method name
|
|
159
|
+
const nameNode = node.childForFieldName('name');
|
|
160
|
+
if (nameNode) {
|
|
161
|
+
names.push(nameNode.text);
|
|
162
|
+
}
|
|
163
|
+
// Get arguments
|
|
164
|
+
const argsNode = node.childForFieldName('arguments');
|
|
165
|
+
if (argsNode) {
|
|
166
|
+
const argList = [];
|
|
167
|
+
for (const child of argsNode.children) {
|
|
168
|
+
if (child.type !== '(' && child.type !== ')' && child.type !== ',') {
|
|
169
|
+
argList.push(child);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
args.push(argList);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
args.push([]);
|
|
176
|
+
}
|
|
177
|
+
// Check if there's a chained call
|
|
178
|
+
let parent = node.parent;
|
|
179
|
+
while (parent) {
|
|
180
|
+
if (parent.type === 'member_call_expression') {
|
|
181
|
+
const chainNameNode = parent.childForFieldName('name');
|
|
182
|
+
if (chainNameNode) {
|
|
183
|
+
names.push(chainNameNode.text);
|
|
184
|
+
}
|
|
185
|
+
const chainArgsNode = parent.childForFieldName('arguments');
|
|
186
|
+
if (chainArgsNode) {
|
|
187
|
+
const argList = [];
|
|
188
|
+
for (const child of chainArgsNode.children) {
|
|
189
|
+
if (child.type !== '(' && child.type !== ')' && child.type !== ',') {
|
|
190
|
+
argList.push(child);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
args.push(argList);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
args.push([]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
parent = parent.parent;
|
|
200
|
+
}
|
|
201
|
+
return { className, names, args };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Try to match Laravel Eloquent static pattern
|
|
205
|
+
* e.g., User::where('active', true)->get(), User::find(1)
|
|
206
|
+
*/
|
|
207
|
+
tryEloquentStaticPattern(chain, node, filePath) {
|
|
208
|
+
const { className, names, args } = chain;
|
|
209
|
+
if (!className || names.length === 0)
|
|
210
|
+
return null;
|
|
211
|
+
// Skip if it's DB:: (handled separately)
|
|
212
|
+
if (className === 'DB')
|
|
213
|
+
return null;
|
|
214
|
+
// Check if it looks like a model (PascalCase, not a framework class)
|
|
215
|
+
if (!/^[A-Z]/.test(className))
|
|
216
|
+
return null;
|
|
217
|
+
const frameworkClasses = ['App', 'Auth', 'Cache', 'Config', 'Cookie', 'Crypt',
|
|
218
|
+
'Event', 'File', 'Gate', 'Hash', 'Http', 'Lang',
|
|
219
|
+
'Log', 'Mail', 'Notification', 'Queue', 'Redirect',
|
|
220
|
+
'Request', 'Response', 'Route', 'Schema', 'Session',
|
|
221
|
+
'Storage', 'URL', 'Validator', 'View'];
|
|
222
|
+
if (frameworkClasses.includes(className))
|
|
223
|
+
return null;
|
|
224
|
+
const eloquentMethods = {
|
|
225
|
+
read: ['find', 'findOrFail', 'findMany', 'findOrNew', 'first', 'firstOrFail',
|
|
226
|
+
'firstOrNew', 'firstOrCreate', 'firstWhere', 'get', 'all', 'pluck',
|
|
227
|
+
'value', 'count', 'max', 'min', 'avg', 'sum', 'exists', 'doesntExist',
|
|
228
|
+
'where', 'whereIn', 'whereNotIn', 'whereBetween', 'whereNull',
|
|
229
|
+
'whereNotNull', 'whereDate', 'whereMonth', 'whereDay', 'whereYear',
|
|
230
|
+
'whereTime', 'whereColumn', 'orWhere', 'orderBy', 'latest', 'oldest',
|
|
231
|
+
'take', 'limit', 'skip', 'offset', 'with', 'withCount', 'has', 'whereHas'],
|
|
232
|
+
write: ['create', 'insert', 'insertOrIgnore', 'insertGetId', 'update',
|
|
233
|
+
'updateOrCreate', 'updateOrInsert', 'upsert', 'increment', 'decrement'],
|
|
234
|
+
delete: ['delete', 'destroy', 'forceDelete', 'truncate'],
|
|
235
|
+
};
|
|
236
|
+
// Where clause methods that take field as first argument
|
|
237
|
+
const whereClauseMethods = ['where', 'whereIn', 'whereNotIn', 'whereBetween',
|
|
238
|
+
'whereNull', 'whereNotNull', 'whereDate', 'whereMonth',
|
|
239
|
+
'whereDay', 'whereYear', 'whereTime', 'whereColumn',
|
|
240
|
+
'orWhere', 'firstWhere', 'whereHas'];
|
|
241
|
+
let operation = null;
|
|
242
|
+
const whereFields = [];
|
|
243
|
+
const firstMethod = names[0];
|
|
244
|
+
// Check all methods in chain for operation type and extract where fields
|
|
245
|
+
for (let i = 0; i < names.length; i++) {
|
|
246
|
+
const method = names[i];
|
|
247
|
+
const methodArgs = args[i];
|
|
248
|
+
if (!method)
|
|
249
|
+
continue;
|
|
250
|
+
if (eloquentMethods.write.includes(method)) {
|
|
251
|
+
operation = 'write';
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
if (eloquentMethods.delete.includes(method)) {
|
|
255
|
+
operation = 'delete';
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
// Extract fields from where clause methods
|
|
259
|
+
if (whereClauseMethods.includes(method) && methodArgs && methodArgs.length > 0) {
|
|
260
|
+
const fieldName = this.extractWhereClauseField(methodArgs);
|
|
261
|
+
if (fieldName) {
|
|
262
|
+
whereFields.push(fieldName);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Default to read if we have query methods
|
|
267
|
+
if (!operation && firstMethod) {
|
|
268
|
+
if (eloquentMethods.read.includes(firstMethod)) {
|
|
269
|
+
operation = 'read';
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!operation)
|
|
273
|
+
return null;
|
|
274
|
+
return this.createAccessPoint({
|
|
275
|
+
table: this.inferTableFromName(className),
|
|
276
|
+
fields: [...new Set(whereFields)],
|
|
277
|
+
operation,
|
|
278
|
+
file: filePath,
|
|
279
|
+
line: node.startPosition.row + 1,
|
|
280
|
+
column: node.startPosition.column,
|
|
281
|
+
context: node.text.slice(0, 200),
|
|
282
|
+
confidence: 0.95,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Extract field name from a where clause method call
|
|
287
|
+
*/
|
|
288
|
+
extractWhereClauseField(args) {
|
|
289
|
+
if (args.length === 0)
|
|
290
|
+
return null;
|
|
291
|
+
const firstArg = args[0];
|
|
292
|
+
if (!firstArg)
|
|
293
|
+
return null;
|
|
294
|
+
// Handle argument wrapper
|
|
295
|
+
const actualArg = firstArg.type === 'argument' ? firstArg.namedChildren[0] : firstArg;
|
|
296
|
+
if (!actualArg)
|
|
297
|
+
return null;
|
|
298
|
+
// String argument: ->where('email', value)
|
|
299
|
+
const strValue = this.extractStringValue(actualArg);
|
|
300
|
+
if (strValue) {
|
|
301
|
+
return strValue;
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Try to match Laravel DB facade pattern
|
|
307
|
+
* e.g., DB::table('users')->get(), DB::select("SELECT...")
|
|
308
|
+
*/
|
|
309
|
+
tryLaravelDbPattern(chain, node, filePath) {
|
|
310
|
+
const { className, names, args } = chain;
|
|
311
|
+
if (className !== 'DB')
|
|
312
|
+
return null;
|
|
313
|
+
if (names.length === 0)
|
|
314
|
+
return null;
|
|
315
|
+
const firstMethod = names[0];
|
|
316
|
+
const firstArgs = args[0];
|
|
317
|
+
// DB::table('users') pattern
|
|
318
|
+
if (firstMethod === 'table' && firstArgs && firstArgs.length > 0 && firstArgs[0]) {
|
|
319
|
+
const tableArg = firstArgs[0];
|
|
320
|
+
const table = this.extractStringValue(tableArg);
|
|
321
|
+
if (!table)
|
|
322
|
+
return null;
|
|
323
|
+
// Where clause methods
|
|
324
|
+
const whereClauseMethods = ['where', 'whereIn', 'whereNotIn', 'whereBetween',
|
|
325
|
+
'whereNull', 'whereNotNull', 'orWhere'];
|
|
326
|
+
// Determine operation from chained methods and extract where fields
|
|
327
|
+
let operation = 'read';
|
|
328
|
+
const whereFields = [];
|
|
329
|
+
for (let i = 1; i < names.length; i++) {
|
|
330
|
+
const method = names[i];
|
|
331
|
+
const methodArgs = args[i];
|
|
332
|
+
if (!method)
|
|
333
|
+
continue;
|
|
334
|
+
if (['insert', 'insertOrIgnore', 'insertGetId', 'update', 'upsert',
|
|
335
|
+
'increment', 'decrement'].includes(method)) {
|
|
336
|
+
operation = 'write';
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
if (['delete', 'truncate'].includes(method)) {
|
|
340
|
+
operation = 'delete';
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
// Extract fields from where clause methods
|
|
344
|
+
if (whereClauseMethods.includes(method) && methodArgs && methodArgs.length > 0) {
|
|
345
|
+
const fieldName = this.extractWhereClauseField(methodArgs);
|
|
346
|
+
if (fieldName) {
|
|
347
|
+
whereFields.push(fieldName);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return this.createAccessPoint({
|
|
352
|
+
table,
|
|
353
|
+
fields: [...new Set(whereFields)],
|
|
354
|
+
operation,
|
|
355
|
+
file: filePath,
|
|
356
|
+
line: node.startPosition.row + 1,
|
|
357
|
+
column: node.startPosition.column,
|
|
358
|
+
context: node.text.slice(0, 200),
|
|
359
|
+
confidence: 0.95,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
// DB::select(), DB::insert(), DB::update(), DB::delete() with raw SQL
|
|
363
|
+
const rawMethods = {
|
|
364
|
+
read: ['select', 'selectOne'],
|
|
365
|
+
write: ['insert', 'update', 'statement'],
|
|
366
|
+
delete: ['delete'],
|
|
367
|
+
};
|
|
368
|
+
let operation = null;
|
|
369
|
+
if (rawMethods.read.includes(firstMethod ?? ''))
|
|
370
|
+
operation = 'read';
|
|
371
|
+
else if (rawMethods.write.includes(firstMethod ?? ''))
|
|
372
|
+
operation = 'write';
|
|
373
|
+
else if (rawMethods.delete.includes(firstMethod ?? ''))
|
|
374
|
+
operation = 'delete';
|
|
375
|
+
if (!operation)
|
|
376
|
+
return null;
|
|
377
|
+
// Try to extract table from SQL
|
|
378
|
+
let table = 'unknown';
|
|
379
|
+
if (firstArgs && firstArgs.length > 0 && firstArgs[0]) {
|
|
380
|
+
const sqlArg = firstArgs[0];
|
|
381
|
+
const sqlText = this.extractStringValue(sqlArg);
|
|
382
|
+
if (sqlText) {
|
|
383
|
+
const parsed = this.parseSQLStatement(sqlText);
|
|
384
|
+
table = parsed.table;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return this.createAccessPoint({
|
|
388
|
+
table,
|
|
389
|
+
fields: [],
|
|
390
|
+
operation,
|
|
391
|
+
file: filePath,
|
|
392
|
+
line: node.startPosition.row + 1,
|
|
393
|
+
column: node.startPosition.column,
|
|
394
|
+
context: node.text.slice(0, 200),
|
|
395
|
+
isRawSql: true,
|
|
396
|
+
confidence: 0.85,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Try to match Eloquent instance pattern
|
|
401
|
+
* e.g., $user->save(), $user->delete()
|
|
402
|
+
*/
|
|
403
|
+
tryEloquentInstancePattern(chain, node, filePath) {
|
|
404
|
+
if (chain.names.length < 2)
|
|
405
|
+
return null;
|
|
406
|
+
const methodName = chain.names[chain.names.length - 1];
|
|
407
|
+
if (!methodName)
|
|
408
|
+
return null;
|
|
409
|
+
const instanceMethods = {
|
|
410
|
+
write: ['save', 'update', 'push', 'touch', 'increment', 'decrement',
|
|
411
|
+
'fill', 'forceFill', 'refresh', 'replicate'],
|
|
412
|
+
delete: ['delete', 'forceDelete', 'destroy'],
|
|
413
|
+
};
|
|
414
|
+
let operation = null;
|
|
415
|
+
if (instanceMethods.write.includes(methodName)) {
|
|
416
|
+
operation = 'write';
|
|
417
|
+
}
|
|
418
|
+
else if (instanceMethods.delete.includes(methodName)) {
|
|
419
|
+
operation = 'delete';
|
|
420
|
+
}
|
|
421
|
+
if (!operation)
|
|
422
|
+
return null;
|
|
423
|
+
// Try to infer model from variable name
|
|
424
|
+
const varName = chain.names[0] ?? 'unknown';
|
|
425
|
+
const table = this.inferTableFromName(varName);
|
|
426
|
+
return this.createAccessPoint({
|
|
427
|
+
table,
|
|
428
|
+
fields: [],
|
|
429
|
+
operation,
|
|
430
|
+
file: filePath,
|
|
431
|
+
line: node.startPosition.row + 1,
|
|
432
|
+
column: node.startPosition.column,
|
|
433
|
+
context: node.text.slice(0, 200),
|
|
434
|
+
confidence: 0.8,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Try to match Doctrine ORM pattern
|
|
439
|
+
* e.g., $em->getRepository(User::class)->find(1)
|
|
440
|
+
*/
|
|
441
|
+
tryDoctrinePattern(chain, node, filePath) {
|
|
442
|
+
// Look for EntityManager patterns
|
|
443
|
+
const hasGetRepository = chain.names.includes('getRepository');
|
|
444
|
+
const hasFind = chain.names.includes('find') || chain.names.includes('findOneBy') ||
|
|
445
|
+
chain.names.includes('findBy') || chain.names.includes('findAll');
|
|
446
|
+
const hasPersist = chain.names.includes('persist');
|
|
447
|
+
const hasRemove = chain.names.includes('remove');
|
|
448
|
+
const hasFlush = chain.names.includes('flush');
|
|
449
|
+
if (!hasGetRepository && !hasPersist && !hasRemove)
|
|
450
|
+
return null;
|
|
451
|
+
let operation = 'read';
|
|
452
|
+
if (hasPersist || hasFlush)
|
|
453
|
+
operation = 'write';
|
|
454
|
+
if (hasRemove)
|
|
455
|
+
operation = 'delete';
|
|
456
|
+
if (hasFind)
|
|
457
|
+
operation = 'read';
|
|
458
|
+
// Try to extract entity class from getRepository(Entity::class)
|
|
459
|
+
let table = 'unknown';
|
|
460
|
+
const repoIdx = chain.names.indexOf('getRepository');
|
|
461
|
+
if (repoIdx !== -1 && chain.args[repoIdx]) {
|
|
462
|
+
const repoArgs = chain.args[repoIdx];
|
|
463
|
+
if (repoArgs && repoArgs.length > 0 && repoArgs[0]) {
|
|
464
|
+
const entityArg = repoArgs[0];
|
|
465
|
+
if (entityArg.type === 'class_constant_access_expression') {
|
|
466
|
+
const classNode = entityArg.childForFieldName('class');
|
|
467
|
+
if (classNode) {
|
|
468
|
+
table = this.inferTableFromName(classNode.text);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return this.createAccessPoint({
|
|
474
|
+
table,
|
|
475
|
+
fields: [],
|
|
476
|
+
operation,
|
|
477
|
+
file: filePath,
|
|
478
|
+
line: node.startPosition.row + 1,
|
|
479
|
+
column: node.startPosition.column,
|
|
480
|
+
context: node.text.slice(0, 200),
|
|
481
|
+
confidence: 0.9,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Try to match PDO pattern
|
|
486
|
+
* e.g., $pdo->query("SELECT..."), $stmt->execute()
|
|
487
|
+
*/
|
|
488
|
+
tryPdoPattern(chain, node, filePath) {
|
|
489
|
+
if (chain.names.length < 2)
|
|
490
|
+
return null;
|
|
491
|
+
const varName = chain.names[0]?.toLowerCase() ?? '';
|
|
492
|
+
const methodName = chain.names[1];
|
|
493
|
+
// Check if it looks like PDO
|
|
494
|
+
const isPdo = varName.includes('pdo') || varName.includes('db') ||
|
|
495
|
+
varName.includes('conn') || varName.includes('stmt');
|
|
496
|
+
if (!isPdo)
|
|
497
|
+
return null;
|
|
498
|
+
const pdoMethods = {
|
|
499
|
+
read: ['query', 'prepare'],
|
|
500
|
+
write: ['exec', 'execute'],
|
|
501
|
+
};
|
|
502
|
+
let operation = null;
|
|
503
|
+
if (pdoMethods.read.includes(methodName ?? ''))
|
|
504
|
+
operation = 'read';
|
|
505
|
+
else if (pdoMethods.write.includes(methodName ?? ''))
|
|
506
|
+
operation = 'write';
|
|
507
|
+
if (!operation)
|
|
508
|
+
return null;
|
|
509
|
+
// Try to extract SQL
|
|
510
|
+
let table = 'unknown';
|
|
511
|
+
const methodArgs = chain.args[1];
|
|
512
|
+
if (methodArgs && methodArgs.length > 0) {
|
|
513
|
+
const sqlArg = methodArgs[0];
|
|
514
|
+
if (sqlArg) {
|
|
515
|
+
const sqlText = this.extractStringValue(sqlArg);
|
|
516
|
+
if (sqlText) {
|
|
517
|
+
const parsed = this.parseSQLStatement(sqlText);
|
|
518
|
+
table = parsed.table;
|
|
519
|
+
if (parsed.operation !== 'unknown') {
|
|
520
|
+
operation = parsed.operation;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return this.createAccessPoint({
|
|
526
|
+
table,
|
|
527
|
+
fields: [],
|
|
528
|
+
operation,
|
|
529
|
+
file: filePath,
|
|
530
|
+
line: node.startPosition.row + 1,
|
|
531
|
+
column: node.startPosition.column,
|
|
532
|
+
context: node.text.slice(0, 200),
|
|
533
|
+
isRawSql: true,
|
|
534
|
+
confidence: 0.8,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Extract string value from a node
|
|
539
|
+
*/
|
|
540
|
+
extractStringValue(node) {
|
|
541
|
+
if (!node)
|
|
542
|
+
return null;
|
|
543
|
+
// Handle argument wrapper
|
|
544
|
+
if (node.type === 'argument') {
|
|
545
|
+
const child = node.namedChildren[0];
|
|
546
|
+
if (child)
|
|
547
|
+
return this.extractStringValue(child);
|
|
548
|
+
}
|
|
549
|
+
if (node.type === 'string' || node.type === 'encapsed_string') {
|
|
550
|
+
// Remove quotes
|
|
551
|
+
return node.text.replace(/^['"]|['"]$/g, '');
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Parse a SQL statement to extract table, operation, and fields
|
|
557
|
+
*/
|
|
558
|
+
parseSQLStatement(sql) {
|
|
559
|
+
const upperSql = sql.toUpperCase().trim();
|
|
560
|
+
let operation = 'unknown';
|
|
561
|
+
let table = 'unknown';
|
|
562
|
+
const fields = [];
|
|
563
|
+
if (upperSql.startsWith('SELECT'))
|
|
564
|
+
operation = 'read';
|
|
565
|
+
else if (upperSql.startsWith('INSERT'))
|
|
566
|
+
operation = 'write';
|
|
567
|
+
else if (upperSql.startsWith('UPDATE'))
|
|
568
|
+
operation = 'write';
|
|
569
|
+
else if (upperSql.startsWith('DELETE'))
|
|
570
|
+
operation = 'delete';
|
|
571
|
+
const fromMatch = sql.match(/FROM\s+["'`]?(\w+)["'`]?/i);
|
|
572
|
+
const intoMatch = sql.match(/INTO\s+["'`]?(\w+)["'`]?/i);
|
|
573
|
+
const updateMatch = sql.match(/UPDATE\s+["'`]?(\w+)["'`]?/i);
|
|
574
|
+
if (fromMatch?.[1])
|
|
575
|
+
table = fromMatch[1];
|
|
576
|
+
else if (intoMatch?.[1])
|
|
577
|
+
table = intoMatch[1];
|
|
578
|
+
else if (updateMatch?.[1])
|
|
579
|
+
table = updateMatch[1];
|
|
580
|
+
return { table, operation, fields };
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Create a PHP data access extractor
|
|
585
|
+
*/
|
|
586
|
+
export function createPhpDataAccessExtractor() {
|
|
587
|
+
return new PhpDataAccessExtractor();
|
|
588
|
+
}
|
|
589
|
+
//# sourceMappingURL=php-data-access-extractor.js.map
|