driftdetect-core 0.4.0 → 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.
Files changed (237) hide show
  1. package/dist/boundaries/boundary-scanner.d.ts +76 -0
  2. package/dist/boundaries/boundary-scanner.d.ts.map +1 -0
  3. package/dist/boundaries/boundary-scanner.js +801 -0
  4. package/dist/boundaries/boundary-scanner.js.map +1 -0
  5. package/dist/boundaries/data-access-learner.d.ts +126 -0
  6. package/dist/boundaries/data-access-learner.d.ts.map +1 -0
  7. package/dist/boundaries/data-access-learner.js +486 -0
  8. package/dist/boundaries/data-access-learner.js.map +1 -0
  9. package/dist/boundaries/index.d.ts +6 -0
  10. package/dist/boundaries/index.d.ts.map +1 -1
  11. package/dist/boundaries/index.js +6 -0
  12. package/dist/boundaries/index.js.map +1 -1
  13. package/dist/boundaries/security-prioritizer.d.ts +118 -0
  14. package/dist/boundaries/security-prioritizer.d.ts.map +1 -0
  15. package/dist/boundaries/security-prioritizer.js +316 -0
  16. package/dist/boundaries/security-prioritizer.js.map +1 -0
  17. package/dist/call-graph/analysis/coverage-analyzer.d.ts +201 -0
  18. package/dist/call-graph/analysis/coverage-analyzer.d.ts.map +1 -0
  19. package/dist/call-graph/analysis/coverage-analyzer.js +553 -0
  20. package/dist/call-graph/analysis/coverage-analyzer.js.map +1 -0
  21. package/dist/call-graph/analysis/dead-code-detector.d.ts +145 -0
  22. package/dist/call-graph/analysis/dead-code-detector.d.ts.map +1 -0
  23. package/dist/call-graph/analysis/dead-code-detector.js +391 -0
  24. package/dist/call-graph/analysis/dead-code-detector.js.map +1 -0
  25. package/dist/call-graph/analysis/graph-builder.d.ts +142 -0
  26. package/dist/call-graph/analysis/graph-builder.d.ts.map +1 -0
  27. package/dist/call-graph/analysis/graph-builder.js +624 -0
  28. package/dist/call-graph/analysis/graph-builder.js.map +1 -0
  29. package/dist/call-graph/analysis/impact-analyzer.d.ts +150 -0
  30. package/dist/call-graph/analysis/impact-analyzer.d.ts.map +1 -0
  31. package/dist/call-graph/analysis/impact-analyzer.js +329 -0
  32. package/dist/call-graph/analysis/impact-analyzer.js.map +1 -0
  33. package/dist/call-graph/analysis/index.d.ts +11 -0
  34. package/dist/call-graph/analysis/index.d.ts.map +1 -0
  35. package/dist/call-graph/analysis/index.js +9 -0
  36. package/dist/call-graph/analysis/index.js.map +1 -0
  37. package/dist/call-graph/analysis/path-finder.d.ts +117 -0
  38. package/dist/call-graph/analysis/path-finder.d.ts.map +1 -0
  39. package/dist/call-graph/analysis/path-finder.js +360 -0
  40. package/dist/call-graph/analysis/path-finder.js.map +1 -0
  41. package/dist/call-graph/analysis/reachability.d.ts +56 -0
  42. package/dist/call-graph/analysis/reachability.d.ts.map +1 -0
  43. package/dist/call-graph/analysis/reachability.js +357 -0
  44. package/dist/call-graph/analysis/reachability.js.map +1 -0
  45. package/dist/call-graph/demo.d.ts +11 -0
  46. package/dist/call-graph/demo.d.ts.map +1 -0
  47. package/dist/call-graph/demo.js +339 -0
  48. package/dist/call-graph/demo.js.map +1 -0
  49. package/dist/call-graph/enrichment/enrichment-engine.d.ts +126 -0
  50. package/dist/call-graph/enrichment/enrichment-engine.d.ts.map +1 -0
  51. package/dist/call-graph/enrichment/enrichment-engine.js +760 -0
  52. package/dist/call-graph/enrichment/enrichment-engine.js.map +1 -0
  53. package/dist/call-graph/enrichment/impact-scorer.d.ts +59 -0
  54. package/dist/call-graph/enrichment/impact-scorer.d.ts.map +1 -0
  55. package/dist/call-graph/enrichment/impact-scorer.js +328 -0
  56. package/dist/call-graph/enrichment/impact-scorer.js.map +1 -0
  57. package/dist/call-graph/enrichment/index.d.ts +12 -0
  58. package/dist/call-graph/enrichment/index.d.ts.map +1 -0
  59. package/dist/call-graph/enrichment/index.js +15 -0
  60. package/dist/call-graph/enrichment/index.js.map +1 -0
  61. package/dist/call-graph/enrichment/remediation-generator.d.ts +41 -0
  62. package/dist/call-graph/enrichment/remediation-generator.d.ts.map +1 -0
  63. package/dist/call-graph/enrichment/remediation-generator.js +609 -0
  64. package/dist/call-graph/enrichment/remediation-generator.js.map +1 -0
  65. package/dist/call-graph/enrichment/sensitivity-classifier.d.ts +71 -0
  66. package/dist/call-graph/enrichment/sensitivity-classifier.d.ts.map +1 -0
  67. package/dist/call-graph/enrichment/sensitivity-classifier.js +454 -0
  68. package/dist/call-graph/enrichment/sensitivity-classifier.js.map +1 -0
  69. package/dist/call-graph/enrichment/types.d.ts +402 -0
  70. package/dist/call-graph/enrichment/types.d.ts.map +1 -0
  71. package/dist/call-graph/enrichment/types.js +9 -0
  72. package/dist/call-graph/enrichment/types.js.map +1 -0
  73. package/dist/call-graph/extractors/base-extractor.d.ts +112 -0
  74. package/dist/call-graph/extractors/base-extractor.d.ts.map +1 -0
  75. package/dist/call-graph/extractors/base-extractor.js +140 -0
  76. package/dist/call-graph/extractors/base-extractor.js.map +1 -0
  77. package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts +76 -0
  78. package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts.map +1 -0
  79. package/dist/call-graph/extractors/csharp-data-access-extractor.js +387 -0
  80. package/dist/call-graph/extractors/csharp-data-access-extractor.js.map +1 -0
  81. package/dist/call-graph/extractors/csharp-extractor.d.ts +87 -0
  82. package/dist/call-graph/extractors/csharp-extractor.d.ts.map +1 -0
  83. package/dist/call-graph/extractors/csharp-extractor.js +470 -0
  84. package/dist/call-graph/extractors/csharp-extractor.js.map +1 -0
  85. package/dist/call-graph/extractors/data-access-extractor.d.ts +76 -0
  86. package/dist/call-graph/extractors/data-access-extractor.d.ts.map +1 -0
  87. package/dist/call-graph/extractors/data-access-extractor.js +234 -0
  88. package/dist/call-graph/extractors/data-access-extractor.js.map +1 -0
  89. package/dist/call-graph/extractors/index.d.ts +26 -0
  90. package/dist/call-graph/extractors/index.d.ts.map +1 -0
  91. package/dist/call-graph/extractors/index.js +36 -0
  92. package/dist/call-graph/extractors/index.js.map +1 -0
  93. package/dist/call-graph/extractors/java-data-access-extractor.d.ts +101 -0
  94. package/dist/call-graph/extractors/java-data-access-extractor.d.ts.map +1 -0
  95. package/dist/call-graph/extractors/java-data-access-extractor.js +611 -0
  96. package/dist/call-graph/extractors/java-data-access-extractor.js.map +1 -0
  97. package/dist/call-graph/extractors/java-extractor.d.ts +87 -0
  98. package/dist/call-graph/extractors/java-extractor.d.ts.map +1 -0
  99. package/dist/call-graph/extractors/java-extractor.js +510 -0
  100. package/dist/call-graph/extractors/java-extractor.js.map +1 -0
  101. package/dist/call-graph/extractors/php-data-access-extractor.d.ts +93 -0
  102. package/dist/call-graph/extractors/php-data-access-extractor.d.ts.map +1 -0
  103. package/dist/call-graph/extractors/php-data-access-extractor.js +589 -0
  104. package/dist/call-graph/extractors/php-data-access-extractor.js.map +1 -0
  105. package/dist/call-graph/extractors/php-extractor.d.ts +104 -0
  106. package/dist/call-graph/extractors/php-extractor.d.ts.map +1 -0
  107. package/dist/call-graph/extractors/php-extractor.js +619 -0
  108. package/dist/call-graph/extractors/php-extractor.js.map +1 -0
  109. package/dist/call-graph/extractors/python-data-access-extractor.d.ts +90 -0
  110. package/dist/call-graph/extractors/python-data-access-extractor.d.ts.map +1 -0
  111. package/dist/call-graph/extractors/python-data-access-extractor.js +537 -0
  112. package/dist/call-graph/extractors/python-data-access-extractor.js.map +1 -0
  113. package/dist/call-graph/extractors/python-extractor.d.ts +98 -0
  114. package/dist/call-graph/extractors/python-extractor.d.ts.map +1 -0
  115. package/dist/call-graph/extractors/python-extractor.js +681 -0
  116. package/dist/call-graph/extractors/python-extractor.js.map +1 -0
  117. package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts +91 -0
  118. package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts.map +1 -0
  119. package/dist/call-graph/extractors/semantic-data-access-scanner.js +498 -0
  120. package/dist/call-graph/extractors/semantic-data-access-scanner.js.map +1 -0
  121. package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts +122 -0
  122. package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts.map +1 -0
  123. package/dist/call-graph/extractors/typescript-data-access-extractor.js +788 -0
  124. package/dist/call-graph/extractors/typescript-data-access-extractor.js.map +1 -0
  125. package/dist/call-graph/extractors/typescript-extractor.d.ts +145 -0
  126. package/dist/call-graph/extractors/typescript-extractor.d.ts.map +1 -0
  127. package/dist/call-graph/extractors/typescript-extractor.js +904 -0
  128. package/dist/call-graph/extractors/typescript-extractor.js.map +1 -0
  129. package/dist/call-graph/index.d.ts +127 -0
  130. package/dist/call-graph/index.d.ts.map +1 -0
  131. package/dist/call-graph/index.js +247 -0
  132. package/dist/call-graph/index.js.map +1 -0
  133. package/dist/call-graph/store/call-graph-store.d.ts +70 -0
  134. package/dist/call-graph/store/call-graph-store.d.ts.map +1 -0
  135. package/dist/call-graph/store/call-graph-store.js +210 -0
  136. package/dist/call-graph/store/call-graph-store.js.map +1 -0
  137. package/dist/call-graph/store/index.d.ts +7 -0
  138. package/dist/call-graph/store/index.d.ts.map +1 -0
  139. package/dist/call-graph/store/index.js +7 -0
  140. package/dist/call-graph/store/index.js.map +1 -0
  141. package/dist/call-graph/types.d.ts +376 -0
  142. package/dist/call-graph/types.d.ts.map +1 -0
  143. package/dist/call-graph/types.js +8 -0
  144. package/dist/call-graph/types.js.map +1 -0
  145. package/dist/index.d.ts +8 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +12 -0
  148. package/dist/index.js.map +1 -1
  149. package/dist/lake/callgraph-shard-store.d.ts +168 -0
  150. package/dist/lake/callgraph-shard-store.d.ts.map +1 -0
  151. package/dist/lake/callgraph-shard-store.js +466 -0
  152. package/dist/lake/callgraph-shard-store.js.map +1 -0
  153. package/dist/lake/examples-store.d.ts +127 -0
  154. package/dist/lake/examples-store.d.ts.map +1 -0
  155. package/dist/lake/examples-store.js +389 -0
  156. package/dist/lake/examples-store.js.map +1 -0
  157. package/dist/lake/index-store.d.ts +82 -0
  158. package/dist/lake/index-store.d.ts.map +1 -0
  159. package/dist/lake/index-store.js +359 -0
  160. package/dist/lake/index-store.js.map +1 -0
  161. package/dist/lake/index.d.ts +93 -0
  162. package/dist/lake/index.d.ts.map +1 -0
  163. package/dist/lake/index.js +138 -0
  164. package/dist/lake/index.js.map +1 -0
  165. package/dist/lake/lake.bak/index-store.d.ts +82 -0
  166. package/dist/lake/lake.bak/index-store.d.ts.map +1 -0
  167. package/dist/lake/lake.bak/index-store.js +357 -0
  168. package/dist/lake/lake.bak/index-store.js.map +1 -0
  169. package/dist/lake/lake.bak/index.d.ts +81 -0
  170. package/dist/lake/lake.bak/index.d.ts.map +1 -0
  171. package/dist/lake/lake.bak/index.js +114 -0
  172. package/dist/lake/lake.bak/index.js.map +1 -0
  173. package/dist/lake/lake.bak/manifest-store.d.ts +51 -0
  174. package/dist/lake/lake.bak/manifest-store.d.ts.map +1 -0
  175. package/dist/lake/lake.bak/manifest-store.js +347 -0
  176. package/dist/lake/lake.bak/manifest-store.js.map +1 -0
  177. package/dist/lake/lake.bak/query-engine.d.ts +112 -0
  178. package/dist/lake/lake.bak/query-engine.d.ts.map +1 -0
  179. package/dist/lake/lake.bak/query-engine.js +370 -0
  180. package/dist/lake/lake.bak/query-engine.js.map +1 -0
  181. package/dist/lake/lake.bak/types.d.ts +428 -0
  182. package/dist/lake/lake.bak/types.d.ts.map +1 -0
  183. package/dist/lake/lake.bak/types.js +46 -0
  184. package/dist/lake/lake.bak/types.js.map +1 -0
  185. package/dist/lake/lake.bak/view-materializer.d.ts +70 -0
  186. package/dist/lake/lake.bak/view-materializer.d.ts.map +1 -0
  187. package/dist/lake/lake.bak/view-materializer.js +314 -0
  188. package/dist/lake/lake.bak/view-materializer.js.map +1 -0
  189. package/dist/lake/lake.bak/view-store.d.ts +57 -0
  190. package/dist/lake/lake.bak/view-store.d.ts.map +1 -0
  191. package/dist/lake/lake.bak/view-store.js +348 -0
  192. package/dist/lake/lake.bak/view-store.js.map +1 -0
  193. package/dist/lake/manifest-store.d.ts +51 -0
  194. package/dist/lake/manifest-store.d.ts.map +1 -0
  195. package/dist/lake/manifest-store.js +348 -0
  196. package/dist/lake/manifest-store.js.map +1 -0
  197. package/dist/lake/pattern-shard-store.d.ts +87 -0
  198. package/dist/lake/pattern-shard-store.d.ts.map +1 -0
  199. package/dist/lake/pattern-shard-store.js +347 -0
  200. package/dist/lake/pattern-shard-store.js.map +1 -0
  201. package/dist/lake/query-engine.d.ts +124 -0
  202. package/dist/lake/query-engine.d.ts.map +1 -0
  203. package/dist/lake/query-engine.js +453 -0
  204. package/dist/lake/query-engine.js.map +1 -0
  205. package/dist/lake/security-shard-store.d.ts +156 -0
  206. package/dist/lake/security-shard-store.d.ts.map +1 -0
  207. package/dist/lake/security-shard-store.js +498 -0
  208. package/dist/lake/security-shard-store.js.map +1 -0
  209. package/dist/lake/types.d.ts +428 -0
  210. package/dist/lake/types.d.ts.map +1 -0
  211. package/dist/lake/types.js +46 -0
  212. package/dist/lake/types.js.map +1 -0
  213. package/dist/lake/view-materializer.d.ts +70 -0
  214. package/dist/lake/view-materializer.d.ts.map +1 -0
  215. package/dist/lake/view-materializer.js +314 -0
  216. package/dist/lake/view-materializer.js.map +1 -0
  217. package/dist/lake/view-store.d.ts +57 -0
  218. package/dist/lake/view-store.d.ts.map +1 -0
  219. package/dist/lake/view-store.js +348 -0
  220. package/dist/lake/view-store.js.map +1 -0
  221. package/dist/parsers/tree-sitter/index.d.ts +1 -0
  222. package/dist/parsers/tree-sitter/index.d.ts.map +1 -1
  223. package/dist/parsers/tree-sitter/index.js +4 -0
  224. package/dist/parsers/tree-sitter/index.js.map +1 -1
  225. package/dist/parsers/tree-sitter/typescript-loader.d.ts +58 -0
  226. package/dist/parsers/tree-sitter/typescript-loader.d.ts.map +1 -0
  227. package/dist/parsers/tree-sitter/typescript-loader.js +250 -0
  228. package/dist/parsers/tree-sitter/typescript-loader.js.map +1 -0
  229. package/dist/store/project-config.d.ts +154 -0
  230. package/dist/store/project-config.d.ts.map +1 -0
  231. package/dist/store/project-config.js +235 -0
  232. package/dist/store/project-config.js.map +1 -0
  233. package/dist/store/project-registry.d.ts +241 -0
  234. package/dist/store/project-registry.d.ts.map +1 -0
  235. package/dist/store/project-registry.js +557 -0
  236. package/dist/store/project-registry.js.map +1 -0
  237. package/package.json +7 -7
@@ -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