code-auditor-mcp 1.20.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/adapters/GoAdapter.d.ts +53 -0
  2. package/dist/adapters/GoAdapter.d.ts.map +1 -0
  3. package/dist/adapters/GoAdapter.js +602 -0
  4. package/dist/adapters/GoAdapter.js.map +1 -0
  5. package/dist/adapters/GoConfig.d.ts +35 -0
  6. package/dist/adapters/GoConfig.d.ts.map +1 -0
  7. package/dist/adapters/GoConfig.js +155 -0
  8. package/dist/adapters/GoConfig.js.map +1 -0
  9. package/dist/adapters/LanguageAdapter.d.ts +135 -0
  10. package/dist/adapters/LanguageAdapter.d.ts.map +1 -0
  11. package/dist/adapters/LanguageAdapter.js +47 -0
  12. package/dist/adapters/LanguageAdapter.js.map +1 -0
  13. package/dist/adapters/TypeScriptAdapter.d.ts +43 -0
  14. package/dist/adapters/TypeScriptAdapter.d.ts.map +1 -0
  15. package/dist/adapters/TypeScriptAdapter.js +386 -0
  16. package/dist/adapters/TypeScriptAdapter.js.map +1 -0
  17. package/dist/adapters/UniversalAnalyzer.d.ts +42 -0
  18. package/dist/adapters/UniversalAnalyzer.d.ts.map +1 -0
  19. package/dist/adapters/UniversalAnalyzer.js +103 -0
  20. package/dist/adapters/UniversalAnalyzer.js.map +1 -0
  21. package/dist/adapters/UniversalSOLIDAnalyzer.d.ts +48 -0
  22. package/dist/adapters/UniversalSOLIDAnalyzer.d.ts.map +1 -0
  23. package/dist/adapters/UniversalSOLIDAnalyzer.js +178 -0
  24. package/dist/adapters/UniversalSOLIDAnalyzer.js.map +1 -0
  25. package/dist/adapters/solidAnalyzerCompat.d.ts +21 -0
  26. package/dist/adapters/solidAnalyzerCompat.d.ts.map +1 -0
  27. package/dist/adapters/solidAnalyzerCompat.js +78 -0
  28. package/dist/adapters/solidAnalyzerCompat.js.map +1 -0
  29. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +103 -0
  30. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -0
  31. package/dist/analyzers/cross-language/APIContractAnalyzer.js +425 -0
  32. package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -0
  33. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts +117 -0
  34. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -0
  35. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js +618 -0
  36. package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -0
  37. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +129 -0
  38. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -0
  39. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +473 -0
  40. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -0
  41. package/dist/analyzers/cross-language/SchemaValidator.d.ts +126 -0
  42. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -0
  43. package/dist/analyzers/cross-language/SchemaValidator.js +522 -0
  44. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -0
  45. package/dist/analyzers/dataAccessAnalyzerCompat.d.ts +20 -0
  46. package/dist/analyzers/dataAccessAnalyzerCompat.d.ts.map +1 -0
  47. package/dist/analyzers/dataAccessAnalyzerCompat.js +59 -0
  48. package/dist/analyzers/dataAccessAnalyzerCompat.js.map +1 -0
  49. package/dist/analyzers/documentationAnalyzerCompat.d.ts +20 -0
  50. package/dist/analyzers/documentationAnalyzerCompat.d.ts.map +1 -0
  51. package/dist/analyzers/documentationAnalyzerCompat.js +52 -0
  52. package/dist/analyzers/documentationAnalyzerCompat.js.map +1 -0
  53. package/dist/analyzers/dryAnalyzerCompat.d.ts +20 -0
  54. package/dist/analyzers/dryAnalyzerCompat.d.ts.map +1 -0
  55. package/dist/analyzers/dryAnalyzerCompat.js +44 -0
  56. package/dist/analyzers/dryAnalyzerCompat.js.map +1 -0
  57. package/dist/analyzers/schemaAnalyzer.d.ts +21 -0
  58. package/dist/analyzers/schemaAnalyzer.d.ts.map +1 -0
  59. package/dist/analyzers/schemaAnalyzer.js +405 -0
  60. package/dist/analyzers/schemaAnalyzer.js.map +1 -0
  61. package/dist/analyzers/schemaAnalyzerCompat.d.ts +20 -0
  62. package/dist/analyzers/schemaAnalyzerCompat.d.ts.map +1 -0
  63. package/dist/analyzers/schemaAnalyzerCompat.js +69 -0
  64. package/dist/analyzers/schemaAnalyzerCompat.js.map +1 -0
  65. package/dist/analyzers/solidAnalyzerCompat.d.ts +20 -0
  66. package/dist/analyzers/solidAnalyzerCompat.d.ts.map +1 -0
  67. package/dist/analyzers/solidAnalyzerCompat.js +78 -0
  68. package/dist/analyzers/solidAnalyzerCompat.js.map +1 -0
  69. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +71 -0
  70. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -0
  71. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +276 -0
  72. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -0
  73. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +82 -0
  74. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -0
  75. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +489 -0
  76. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -0
  77. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +48 -0
  78. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -0
  79. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +175 -0
  80. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -0
  81. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +64 -0
  82. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -0
  83. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +286 -0
  84. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -0
  85. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +119 -0
  86. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -0
  87. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +812 -0
  88. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -0
  89. package/dist/auditRunner.d.ts.map +1 -1
  90. package/dist/auditRunner.js +23 -7
  91. package/dist/auditRunner.js.map +1 -1
  92. package/dist/codeIndexDB-enhanced.d.ts +80 -0
  93. package/dist/codeIndexDB-enhanced.d.ts.map +1 -0
  94. package/dist/codeIndexDB-enhanced.js +488 -0
  95. package/dist/codeIndexDB-enhanced.js.map +1 -0
  96. package/dist/codeIndexDb.d.ts +70 -1
  97. package/dist/codeIndexDb.d.ts.map +1 -1
  98. package/dist/codeIndexDb.js +199 -2
  99. package/dist/codeIndexDb.js.map +1 -1
  100. package/dist/config/defaults.js +1 -1
  101. package/dist/config/defaults.js.map +1 -1
  102. package/dist/languages/LanguageOrchestrator.d.ts +181 -0
  103. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -0
  104. package/dist/languages/LanguageOrchestrator.js +365 -0
  105. package/dist/languages/LanguageOrchestrator.js.map +1 -0
  106. package/dist/languages/LanguageRegistry.d.ts +43 -0
  107. package/dist/languages/LanguageRegistry.d.ts.map +1 -0
  108. package/dist/languages/LanguageRegistry.js +86 -0
  109. package/dist/languages/LanguageRegistry.js.map +1 -0
  110. package/dist/languages/RuntimeManager.d.ts +181 -0
  111. package/dist/languages/RuntimeManager.d.ts.map +1 -0
  112. package/dist/languages/RuntimeManager.js +841 -0
  113. package/dist/languages/RuntimeManager.js.map +1 -0
  114. package/dist/languages/UniversalAnalyzer.d.ts +37 -0
  115. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -0
  116. package/dist/languages/UniversalAnalyzer.js +101 -0
  117. package/dist/languages/UniversalAnalyzer.js.map +1 -0
  118. package/dist/languages/go/GoAdapter.d.ts +57 -0
  119. package/dist/languages/go/GoAdapter.d.ts.map +1 -0
  120. package/dist/languages/go/GoAdapter.js +360 -0
  121. package/dist/languages/go/GoAdapter.js.map +1 -0
  122. package/dist/languages/index.d.ts +13 -0
  123. package/dist/languages/index.d.ts.map +1 -0
  124. package/dist/languages/index.js +23 -0
  125. package/dist/languages/index.js.map +1 -0
  126. package/dist/languages/types.d.ts +182 -0
  127. package/dist/languages/types.d.ts.map +1 -0
  128. package/dist/languages/types.js +5 -0
  129. package/dist/languages/types.js.map +1 -0
  130. package/dist/languages/typescript/TypeScriptAdapter.d.ts +108 -0
  131. package/dist/languages/typescript/TypeScriptAdapter.d.ts.map +1 -0
  132. package/dist/languages/typescript/TypeScriptAdapter.js +567 -0
  133. package/dist/languages/typescript/TypeScriptAdapter.js.map +1 -0
  134. package/dist/languages/typescript/test-extraction.d.ts +2 -0
  135. package/dist/languages/typescript/test-extraction.d.ts.map +1 -0
  136. package/dist/languages/typescript/test-extraction.js +36 -0
  137. package/dist/languages/typescript/test-extraction.js.map +1 -0
  138. package/dist/mcp-hybrid.d.ts +10 -0
  139. package/dist/mcp-hybrid.d.ts.map +1 -0
  140. package/dist/mcp-hybrid.js +41 -0
  141. package/dist/mcp-hybrid.js.map +1 -0
  142. package/dist/mcp-index.d.ts +11 -0
  143. package/dist/mcp-index.d.ts.map +1 -0
  144. package/dist/mcp-index.js +101 -0
  145. package/dist/mcp-index.js.map +1 -0
  146. package/dist/mcp-tools/workflowGuide.d.ts.map +1 -1
  147. package/dist/mcp-tools/workflowGuide.js +67 -10
  148. package/dist/mcp-tools/workflowGuide.js.map +1 -1
  149. package/dist/mcp-tools-shared.d.ts +49 -0
  150. package/dist/mcp-tools-shared.d.ts.map +1 -0
  151. package/dist/mcp-tools-shared.js +1282 -0
  152. package/dist/mcp-tools-shared.js.map +1 -0
  153. package/dist/mcp-ui-server.d.ts +27 -0
  154. package/dist/mcp-ui-server.d.ts.map +1 -0
  155. package/dist/mcp-ui-server.js +484 -0
  156. package/dist/mcp-ui-server.js.map +1 -0
  157. package/dist/mcp-ui-simple.d.ts +27 -0
  158. package/dist/mcp-ui-simple.d.ts.map +1 -0
  159. package/dist/mcp-ui-simple.js +425 -0
  160. package/dist/mcp-ui-simple.js.map +1 -0
  161. package/dist/mcp.js +7 -4
  162. package/dist/mcp.js.map +1 -1
  163. package/dist/services/SchemaParser.d.ts +89 -0
  164. package/dist/services/SchemaParser.d.ts.map +1 -0
  165. package/dist/services/SchemaParser.js +434 -0
  166. package/dist/services/SchemaParser.js.map +1 -0
  167. package/dist/types/crossLanguage.d.ts +267 -0
  168. package/dist/types/crossLanguage.d.ts.map +1 -0
  169. package/dist/types/crossLanguage.js +6 -0
  170. package/dist/types/crossLanguage.js.map +1 -0
  171. package/dist/types.d.ts +128 -0
  172. package/dist/types.d.ts.map +1 -1
  173. package/dist/types.js.map +1 -1
  174. package/dist/utils/fileDiscovery.d.ts +6 -0
  175. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  176. package/dist/utils/fileDiscovery.js +12 -1
  177. package/dist/utils/fileDiscovery.js.map +1 -1
  178. package/examples/schema-example.json +364 -0
  179. package/examples/schema-example.yaml +137 -0
  180. package/package.json +10 -3
@@ -0,0 +1,812 @@
1
+ /**
2
+ * Universal Schema Analyzer
3
+ * Works across multiple programming languages using the adapter pattern
4
+ * Analyzes code against database schemas
5
+ */
6
+ import { UniversalAnalyzer } from '../../languages/UniversalAnalyzer.js';
7
+ export const DEFAULT_SCHEMA_CONFIG = {
8
+ enableTableUsageTracking: true,
9
+ checkMissingReferences: true,
10
+ checkNamingConventions: true,
11
+ detectUnusedTables: false,
12
+ validateQueryPatterns: true,
13
+ maxQueriesPerFunction: 5,
14
+ requiredSchemas: [],
15
+ schemas: [],
16
+ validateJsonSchemas: true,
17
+ jsonSchemaVersion: 'draft-07',
18
+ allowedJsonTypes: ['string', 'number', 'integer', 'boolean', 'array', 'object', 'null'],
19
+ schemaFilePatterns: ['*.schema.json', '*-schema.json'],
20
+ dataFilePatterns: ['*.data.json', '*.example.json'],
21
+ strictMode: false,
22
+ allowAdditionalProperties: true
23
+ };
24
+ import { promises as fs } from 'fs';
25
+ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
26
+ name = 'schema';
27
+ description = 'Analyzes code against database schemas and validates JSON schemas';
28
+ category = 'database';
29
+ // Track references across files
30
+ tableReferences = new Map();
31
+ columnReferences = new Map();
32
+ /**
33
+ * Override analyze to handle both TypeScript files and JSON files
34
+ */
35
+ async analyze(files, config) {
36
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
37
+ const codeFiles = files.filter(f => !f.endsWith('.json'));
38
+ // Analyze code files using the standard Universal approach
39
+ const codeResult = codeFiles.length > 0 ? await super.analyze(codeFiles, config) : {
40
+ violations: [],
41
+ errors: [],
42
+ filesProcessed: 0,
43
+ executionTime: 0
44
+ };
45
+ // Analyze JSON schema files
46
+ const jsonResult = await this.analyzeJsonSchemas(jsonFiles, config);
47
+ // Combine results
48
+ return {
49
+ violations: [...codeResult.violations, ...jsonResult.violations],
50
+ errors: [...(codeResult.errors || []), ...(jsonResult.errors || [])],
51
+ filesProcessed: codeResult.filesProcessed + jsonResult.filesProcessed,
52
+ executionTime: (codeResult.executionTime || 0) + (jsonResult.executionTime || 0)
53
+ };
54
+ }
55
+ async analyzeAST(ast, adapter, config, sourceCode) {
56
+ const violations = [];
57
+ const finalConfig = { ...DEFAULT_SCHEMA_CONFIG, ...config };
58
+ // Get available schemas
59
+ const schemas = finalConfig.schemas || [];
60
+ const allTables = new Set();
61
+ const tableColumns = new Map();
62
+ // Build lookup maps
63
+ for (const schema of schemas) {
64
+ for (const table of schema.tables) {
65
+ allTables.add(table.name);
66
+ const columns = new Set(table.columns.map(c => c.name));
67
+ tableColumns.set(table.name, columns);
68
+ }
69
+ }
70
+ // Check if schemas are required but missing
71
+ if (finalConfig.requiredSchemas && finalConfig.requiredSchemas.length > 0 && schemas.length === 0) {
72
+ violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'No database schemas loaded for analysis', 'warning', 'missing-schemas'));
73
+ return violations;
74
+ }
75
+ // Find table references in the code
76
+ const tableRefs = this.findTableReferences(ast, adapter, sourceCode);
77
+ const columnRefs = this.findColumnReferences(ast, adapter, sourceCode);
78
+ // Check for missing table references
79
+ if (finalConfig.checkMissingReferences) {
80
+ for (const ref of tableRefs) {
81
+ if (!allTables.has(ref.table) && !this.isSystemTable(ref.table)) {
82
+ violations.push(this.createViolation(ast.filePath, ref.location, `Reference to unknown table '${ref.table}'`, 'warning', 'unknown-table'));
83
+ }
84
+ }
85
+ // Check column references
86
+ for (const ref of columnRefs) {
87
+ const columnSet = tableColumns.get(ref.table);
88
+ if (columnSet && !columnSet.has(ref.column)) {
89
+ violations.push(this.createViolation(ast.filePath, ref.location, `Reference to unknown column '${ref.column}' in table '${ref.table}'`, 'warning', 'unknown-column'));
90
+ }
91
+ }
92
+ }
93
+ // Check naming conventions
94
+ if (finalConfig.checkNamingConventions) {
95
+ violations.push(...this.checkNamingConventions(tableRefs, ast.filePath));
96
+ }
97
+ // Check query patterns
98
+ if (finalConfig.validateQueryPatterns) {
99
+ violations.push(...this.checkQueryPatterns(ast, adapter, sourceCode, finalConfig));
100
+ }
101
+ // Check for SQL injection patterns
102
+ violations.push(...this.checkSQLInjection(ast, adapter, sourceCode));
103
+ return violations;
104
+ }
105
+ /**
106
+ * Find table references in the code
107
+ */
108
+ findTableReferences(ast, adapter, sourceCode) {
109
+ const references = [];
110
+ // Common patterns for table references
111
+ const patterns = [
112
+ // ORM patterns
113
+ /from\s*\(\s*["'`]?(\w+)["'`]?\s*\)/gi,
114
+ /table\s*[:=]\s*["'`](\w+)["'`]/gi,
115
+ /\.(\w+Table)\s*\(/gi,
116
+ // SQL patterns
117
+ /FROM\s+["'`]?(\w+)["'`]?/gi,
118
+ /JOIN\s+["'`]?(\w+)["'`]?/gi,
119
+ /UPDATE\s+["'`]?(\w+)["'`]?/gi,
120
+ /INSERT\s+INTO\s+["'`]?(\w+)["'`]?/gi,
121
+ /DELETE\s+FROM\s+["'`]?(\w+)["'`]?/gi,
122
+ /CREATE\s+TABLE\s+["'`]?(\w+)["'`]?/gi,
123
+ // Schema definition patterns
124
+ /pgTable\s*\(\s*["'`](\w+)["'`]/gi,
125
+ /mysqlTable\s*\(\s*["'`](\w+)["'`]/gi,
126
+ /sqliteTable\s*\(\s*["'`](\w+)["'`]/gi
127
+ ];
128
+ for (const pattern of patterns) {
129
+ let match;
130
+ while ((match = pattern.exec(sourceCode)) !== null) {
131
+ const table = match[1];
132
+ const index = match.index;
133
+ const lines = sourceCode.substring(0, index).split('\n');
134
+ const line = lines.length;
135
+ const column = lines[lines.length - 1].length + 1;
136
+ references.push({
137
+ table,
138
+ type: this.getQueryType(match[0]),
139
+ location: { line, column },
140
+ context: match[0]
141
+ });
142
+ }
143
+ }
144
+ return references;
145
+ }
146
+ /**
147
+ * Find column references
148
+ */
149
+ findColumnReferences(ast, adapter, sourceCode) {
150
+ const references = [];
151
+ // Patterns for column references
152
+ const patterns = [
153
+ /(\w+)\.(\w+)\s*[=<>]/gi, // table.column comparison
154
+ /SELECT\s+.*?(\w+)\.(\w+)/gi, // SELECT table.column
155
+ /WHERE\s+.*?(\w+)\.(\w+)/gi, // WHERE table.column
156
+ ];
157
+ for (const pattern of patterns) {
158
+ let match;
159
+ while ((match = pattern.exec(sourceCode)) !== null) {
160
+ const table = match[1];
161
+ const column = match[2];
162
+ const index = match.index;
163
+ const lines = sourceCode.substring(0, index).split('\n');
164
+ const line = lines.length;
165
+ const col = lines[lines.length - 1].length + 1;
166
+ references.push({
167
+ table,
168
+ column,
169
+ location: { line, column: col }
170
+ });
171
+ }
172
+ }
173
+ return references;
174
+ }
175
+ /**
176
+ * Check naming conventions
177
+ */
178
+ checkNamingConventions(references, filePath) {
179
+ const violations = [];
180
+ for (const ref of references) {
181
+ // Check for camelCase tables (should be snake_case)
182
+ if (/[A-Z]/.test(ref.table) && !ref.table.endsWith('Table')) {
183
+ violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' should use snake_case convention`, 'suggestion', 'naming-convention'));
184
+ }
185
+ // Check for reserved words
186
+ const reserved = ['user', 'order', 'group', 'table', 'column', 'index'];
187
+ if (reserved.includes(ref.table.toLowerCase())) {
188
+ violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' is a reserved word. Consider using a different name.`, 'warning', 'reserved-word'));
189
+ }
190
+ }
191
+ return violations;
192
+ }
193
+ /**
194
+ * Check query patterns
195
+ */
196
+ checkQueryPatterns(ast, adapter, sourceCode, config) {
197
+ const violations = [];
198
+ // Count queries per function
199
+ const functions = adapter.extractFunctions(ast);
200
+ for (const func of functions) {
201
+ const funcNode = this.findNodeByLocation(ast.root, func.location.start);
202
+ if (!funcNode)
203
+ continue;
204
+ const funcText = adapter.getNodeText(funcNode, sourceCode);
205
+ const queryCount = this.countQueries(funcText);
206
+ if (queryCount > (config.maxQueriesPerFunction || 5)) {
207
+ violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' has ${queryCount} queries, exceeding the maximum of ${config.maxQueriesPerFunction}`, 'warning', 'too-many-queries'));
208
+ }
209
+ }
210
+ // Check for N+1 query patterns
211
+ if (sourceCode.includes('.map') && (sourceCode.includes('.query') || sourceCode.includes('SELECT'))) {
212
+ violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'Potential N+1 query pattern detected. Consider using a join or batch query.', 'warning', 'n-plus-one'));
213
+ }
214
+ return violations;
215
+ }
216
+ /**
217
+ * Check for SQL injection vulnerabilities
218
+ */
219
+ checkSQLInjection(ast, adapter, sourceCode) {
220
+ const violations = [];
221
+ // Patterns that indicate potential SQL injection
222
+ const dangerousPatterns = [
223
+ /query\s*\(\s*`[^`]*\$\{[^}]+\}[^`]*`/g, // Template literals in queries
224
+ /query\s*\(\s*['"][^'"]*['"]?\s*\+/g, // String concatenation
225
+ /execute\s*\(\s*['"][^'"]*['"]?\s*\+/g, // String concatenation in execute
226
+ ];
227
+ for (const pattern of dangerousPatterns) {
228
+ if (pattern.test(sourceCode)) {
229
+ violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'Potential SQL injection vulnerability. Use parameterized queries.', 'critical', 'sql-injection'));
230
+ break;
231
+ }
232
+ }
233
+ return violations;
234
+ }
235
+ /**
236
+ * Helper methods
237
+ */
238
+ getQueryType(context) {
239
+ const upper = context.toUpperCase();
240
+ if (upper.includes('SELECT'))
241
+ return 'select';
242
+ if (upper.includes('INSERT'))
243
+ return 'insert';
244
+ if (upper.includes('UPDATE'))
245
+ return 'update';
246
+ if (upper.includes('DELETE'))
247
+ return 'delete';
248
+ if (upper.includes('CREATE'))
249
+ return 'create';
250
+ return 'reference';
251
+ }
252
+ isSystemTable(table) {
253
+ const systemTables = [
254
+ 'information_schema',
255
+ 'pg_catalog',
256
+ 'mysql',
257
+ 'performance_schema',
258
+ 'sys'
259
+ ];
260
+ return systemTables.some(st => table.toLowerCase().startsWith(st));
261
+ }
262
+ countQueries(text) {
263
+ const patterns = [
264
+ /\.query\s*\(/g,
265
+ /\.execute\s*\(/g,
266
+ /SELECT\s+/gi,
267
+ /INSERT\s+INTO/gi,
268
+ /UPDATE\s+/gi,
269
+ /DELETE\s+FROM/gi
270
+ ];
271
+ let count = 0;
272
+ for (const pattern of patterns) {
273
+ const matches = text.match(pattern);
274
+ if (matches)
275
+ count += matches.length;
276
+ }
277
+ return count;
278
+ }
279
+ findNodeByLocation(root, location) {
280
+ const queue = [root];
281
+ while (queue.length > 0) {
282
+ const node = queue.shift();
283
+ if (node.location.start.line === location.line &&
284
+ node.location.start.column === location.column) {
285
+ return node;
286
+ }
287
+ if (node.children) {
288
+ queue.push(...node.children);
289
+ }
290
+ }
291
+ return null;
292
+ }
293
+ /**
294
+ * Analyze JSON Schema files
295
+ */
296
+ async analyzeJsonSchemas(files, config) {
297
+ const violations = [];
298
+ const errors = [];
299
+ let filesProcessed = 0;
300
+ const startTime = Date.now();
301
+ const finalConfig = { ...DEFAULT_SCHEMA_CONFIG, ...config };
302
+ if (!finalConfig.validateJsonSchemas) {
303
+ return { violations, errors, filesProcessed, executionTime: 0 };
304
+ }
305
+ // Categorize files
306
+ const schemaFiles = this.identifySchemaFiles(files, finalConfig);
307
+ const dataFiles = this.identifyDataFiles(files, finalConfig);
308
+ const unknownJsonFiles = files.filter(f => !schemaFiles.includes(f) && !dataFiles.includes(f));
309
+ // Load and validate schema files
310
+ const schemas = new Map();
311
+ for (const file of schemaFiles) {
312
+ try {
313
+ const content = await fs.readFile(file, 'utf8');
314
+ const schema = JSON.parse(content);
315
+ schemas.set(file, schema);
316
+ // Validate the JSON Schema itself
317
+ const fileViolations = this.validateJsonSchema(schema, file, finalConfig);
318
+ violations.push(...fileViolations);
319
+ filesProcessed++;
320
+ }
321
+ catch (error) {
322
+ if (error instanceof SyntaxError) {
323
+ violations.push({
324
+ file,
325
+ line: 1,
326
+ column: 1,
327
+ severity: 'critical',
328
+ message: `Invalid JSON: ${error.message}`,
329
+ rule: 'invalid-json',
330
+ analyzer: 'schema'
331
+ });
332
+ }
333
+ else {
334
+ errors.push({
335
+ file,
336
+ error: error instanceof Error ? error.message : String(error)
337
+ });
338
+ }
339
+ filesProcessed++;
340
+ }
341
+ }
342
+ // Validate data files against schemas
343
+ if (finalConfig.schemaDataPairs) {
344
+ // Use explicit schema-data pairs
345
+ for (const pair of finalConfig.schemaDataPairs) {
346
+ const schema = schemas.get(pair.schema) || await this.loadSchema(pair.schema);
347
+ if (schema) {
348
+ const dataFiles = Array.isArray(pair.data) ? pair.data : [pair.data];
349
+ for (const dataFile of dataFiles) {
350
+ if (files.includes(dataFile)) {
351
+ const dataViolations = await this.validateDataAgainstSchema(dataFile, schema, finalConfig);
352
+ violations.push(...dataViolations);
353
+ filesProcessed++;
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+ else {
360
+ // Try to match data files with schemas based on naming conventions
361
+ for (const dataFile of [...dataFiles, ...unknownJsonFiles]) {
362
+ const matchedSchema = this.findMatchingSchema(dataFile, schemas, finalConfig);
363
+ if (matchedSchema) {
364
+ const dataViolations = await this.validateDataAgainstSchema(dataFile, matchedSchema, finalConfig);
365
+ violations.push(...dataViolations);
366
+ }
367
+ else if (unknownJsonFiles.includes(dataFile)) {
368
+ // For unknown files, just check if they're valid JSON
369
+ try {
370
+ const content = await fs.readFile(dataFile, 'utf8');
371
+ JSON.parse(content);
372
+ }
373
+ catch (error) {
374
+ violations.push({
375
+ file: dataFile,
376
+ line: 1,
377
+ column: 1,
378
+ severity: 'critical',
379
+ message: `Invalid JSON: ${error instanceof Error ? error.message : 'Parse error'}`,
380
+ rule: 'invalid-json',
381
+ analyzer: 'schema'
382
+ });
383
+ }
384
+ }
385
+ filesProcessed++;
386
+ }
387
+ }
388
+ return {
389
+ violations,
390
+ errors,
391
+ filesProcessed,
392
+ executionTime: Date.now() - startTime
393
+ };
394
+ }
395
+ /**
396
+ * Validate a JSON Schema
397
+ */
398
+ validateJsonSchema(schema, filePath, config) {
399
+ const violations = [];
400
+ // Check for $schema property
401
+ if (!schema.$schema && config.jsonSchemaVersion) {
402
+ violations.push({
403
+ file: filePath,
404
+ line: 1,
405
+ column: 1,
406
+ severity: 'suggestion',
407
+ message: 'JSON Schema missing $schema declaration',
408
+ rule: 'missing-schema-declaration',
409
+ analyzer: 'schema'
410
+ });
411
+ }
412
+ // Validate types recursively
413
+ this.validateSchemaTypes(schema, filePath, config, violations);
414
+ // Check for common issues
415
+ if (schema.type === 'object' && schema.properties) {
416
+ // Check for inconsistent required fields
417
+ if (schema.required && Array.isArray(schema.required)) {
418
+ for (const field of schema.required) {
419
+ if (!schema.properties[field]) {
420
+ violations.push({
421
+ file: filePath,
422
+ line: 1,
423
+ column: 1,
424
+ severity: 'critical',
425
+ message: `Required field "${field}" not defined in properties`,
426
+ rule: 'undefined-required-field',
427
+ analyzer: 'schema'
428
+ });
429
+ }
430
+ }
431
+ }
432
+ }
433
+ return violations;
434
+ }
435
+ /**
436
+ * Recursively validate schema types
437
+ */
438
+ validateSchemaTypes(schema, filePath, config, violations, path = '') {
439
+ if (!schema || typeof schema !== 'object')
440
+ return;
441
+ // Check type validity
442
+ if (schema.type && config.allowedJsonTypes) {
443
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
444
+ for (const type of types) {
445
+ if (!config.allowedJsonTypes.includes(type)) {
446
+ violations.push({
447
+ file: filePath,
448
+ line: 1,
449
+ column: 1,
450
+ severity: 'critical',
451
+ message: `Invalid type "${type}" at ${path || 'root'}. Allowed types: ${config.allowedJsonTypes.join(', ')}`,
452
+ rule: 'invalid-type',
453
+ analyzer: 'schema'
454
+ });
455
+ }
456
+ }
457
+ }
458
+ // Check numeric constraints
459
+ if (schema.type === 'integer' || schema.type === 'number') {
460
+ if (schema.minimum !== undefined && schema.maximum !== undefined) {
461
+ if (schema.minimum > schema.maximum) {
462
+ violations.push({
463
+ file: filePath,
464
+ line: 1,
465
+ column: 1,
466
+ severity: 'critical',
467
+ message: `Invalid range at ${path}: minimum (${schema.minimum}) > maximum (${schema.maximum})`,
468
+ rule: 'invalid-range',
469
+ analyzer: 'schema'
470
+ });
471
+ }
472
+ }
473
+ }
474
+ // Recursively check properties
475
+ if (schema.properties) {
476
+ for (const [key, value] of Object.entries(schema.properties)) {
477
+ this.validateSchemaTypes(value, filePath, config, violations, `${path}.${key}`);
478
+ }
479
+ }
480
+ // Check array items
481
+ if (schema.items) {
482
+ this.validateSchemaTypes(schema.items, filePath, config, violations, `${path}[items]`);
483
+ }
484
+ // Check additional properties
485
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
486
+ this.validateSchemaTypes(schema.additionalProperties, filePath, config, violations, `${path}[additionalProperties]`);
487
+ }
488
+ }
489
+ /**
490
+ * Identify schema files based on patterns
491
+ */
492
+ identifySchemaFiles(files, config) {
493
+ if (!config.schemaFilePatterns)
494
+ return [];
495
+ return files.filter(file => {
496
+ const fileName = file.split('/').pop() || '';
497
+ return config.schemaFilePatterns.some(pattern => {
498
+ const regex = new RegExp(pattern.replace('*', '.*'));
499
+ return regex.test(fileName);
500
+ });
501
+ });
502
+ }
503
+ /**
504
+ * Identify data files based on patterns
505
+ */
506
+ identifyDataFiles(files, config) {
507
+ if (!config.dataFilePatterns)
508
+ return [];
509
+ return files.filter(file => {
510
+ const fileName = file.split('/').pop() || '';
511
+ return config.dataFilePatterns.some(pattern => {
512
+ const regex = new RegExp(pattern.replace('*', '.*'));
513
+ return regex.test(fileName);
514
+ });
515
+ });
516
+ }
517
+ /**
518
+ * Load a schema from file
519
+ */
520
+ async loadSchema(schemaPath) {
521
+ try {
522
+ const content = await fs.readFile(schemaPath, 'utf8');
523
+ return JSON.parse(content);
524
+ }
525
+ catch {
526
+ return null;
527
+ }
528
+ }
529
+ /**
530
+ * Find matching schema for a data file
531
+ */
532
+ findMatchingSchema(dataFile, schemas, config) {
533
+ const dataFileName = dataFile.split('/').pop() || '';
534
+ const dataBaseName = dataFileName.replace(/\.(data|example|test)\.json$/, '');
535
+ // Look for schema with matching base name
536
+ for (const [schemaFile, schema] of schemas) {
537
+ const schemaFileName = schemaFile.split('/').pop() || '';
538
+ const schemaBaseName = schemaFileName.replace(/[.-]?schema\.json$/, '');
539
+ if (dataBaseName === schemaBaseName) {
540
+ return schema;
541
+ }
542
+ }
543
+ // If only one schema exists, use it for all data files
544
+ if (schemas.size === 1) {
545
+ return schemas.values().next().value;
546
+ }
547
+ return null;
548
+ }
549
+ /**
550
+ * Validate data against a JSON Schema
551
+ */
552
+ async validateDataAgainstSchema(dataFile, schema, config) {
553
+ const violations = [];
554
+ try {
555
+ const content = await fs.readFile(dataFile, 'utf8');
556
+ const data = JSON.parse(content);
557
+ // Validate the data against the schema
558
+ this.validateAgainstSchema(data, schema, dataFile, violations, config);
559
+ }
560
+ catch (error) {
561
+ if (error instanceof SyntaxError) {
562
+ violations.push({
563
+ file: dataFile,
564
+ line: 1,
565
+ column: 1,
566
+ severity: 'critical',
567
+ message: `Invalid JSON: ${error.message}`,
568
+ rule: 'invalid-json',
569
+ analyzer: 'schema'
570
+ });
571
+ }
572
+ else {
573
+ violations.push({
574
+ file: dataFile,
575
+ line: 1,
576
+ column: 1,
577
+ severity: 'critical',
578
+ message: `Error reading file: ${error instanceof Error ? error.message : 'Unknown error'}`,
579
+ rule: 'file-error',
580
+ analyzer: 'schema'
581
+ });
582
+ }
583
+ }
584
+ return violations;
585
+ }
586
+ /**
587
+ * Validate data against schema recursively
588
+ */
589
+ validateAgainstSchema(data, schema, filePath, violations, config, path = '') {
590
+ // Type validation
591
+ if (schema.type) {
592
+ const actualType = Array.isArray(data) ? 'array' :
593
+ data === null ? 'null' :
594
+ typeof data;
595
+ const allowedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
596
+ // Special handling for integer vs number
597
+ const isValidType = allowedTypes.some(type => {
598
+ if (type === 'integer') {
599
+ return typeof data === 'number' && Number.isInteger(data);
600
+ }
601
+ return type === actualType;
602
+ });
603
+ if (!isValidType) {
604
+ violations.push({
605
+ file: filePath,
606
+ line: 1,
607
+ column: 1,
608
+ severity: 'critical',
609
+ message: `Type mismatch at ${path || 'root'}: expected ${allowedTypes.join(' | ')}, got ${actualType}`,
610
+ rule: 'type-mismatch',
611
+ analyzer: 'schema'
612
+ });
613
+ return; // Don't validate further if type is wrong
614
+ }
615
+ }
616
+ // String validations
617
+ if (schema.type === 'string' && typeof data === 'string') {
618
+ if (schema.minLength !== undefined && data.length < schema.minLength) {
619
+ violations.push({
620
+ file: filePath,
621
+ line: 1,
622
+ column: 1,
623
+ severity: 'warning',
624
+ message: `String at ${path} too short: ${data.length} < ${schema.minLength}`,
625
+ rule: 'string-too-short',
626
+ analyzer: 'schema'
627
+ });
628
+ }
629
+ if (schema.maxLength !== undefined && data.length > schema.maxLength) {
630
+ violations.push({
631
+ file: filePath,
632
+ line: 1,
633
+ column: 1,
634
+ severity: 'warning',
635
+ message: `String at ${path} too long: ${data.length} > ${schema.maxLength}`,
636
+ rule: 'string-too-long',
637
+ analyzer: 'schema'
638
+ });
639
+ }
640
+ if (schema.pattern) {
641
+ const regex = new RegExp(schema.pattern);
642
+ if (!regex.test(data)) {
643
+ violations.push({
644
+ file: filePath,
645
+ line: 1,
646
+ column: 1,
647
+ severity: 'warning',
648
+ message: `String at ${path} doesn't match pattern: ${schema.pattern}`,
649
+ rule: 'pattern-mismatch',
650
+ analyzer: 'schema'
651
+ });
652
+ }
653
+ }
654
+ if (schema.format) {
655
+ // Basic format validation
656
+ switch (schema.format) {
657
+ case 'email':
658
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data)) {
659
+ violations.push({
660
+ file: filePath,
661
+ line: 1,
662
+ column: 1,
663
+ severity: 'warning',
664
+ message: `Invalid email format at ${path}`,
665
+ rule: 'invalid-format',
666
+ analyzer: 'schema'
667
+ });
668
+ }
669
+ break;
670
+ case 'uuid':
671
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(data)) {
672
+ violations.push({
673
+ file: filePath,
674
+ line: 1,
675
+ column: 1,
676
+ severity: 'warning',
677
+ message: `Invalid UUID format at ${path}`,
678
+ rule: 'invalid-format',
679
+ analyzer: 'schema'
680
+ });
681
+ }
682
+ break;
683
+ // Add more formats as needed
684
+ }
685
+ }
686
+ }
687
+ // Number validations
688
+ if ((schema.type === 'number' || schema.type === 'integer') && typeof data === 'number') {
689
+ if (schema.minimum !== undefined && data < schema.minimum) {
690
+ violations.push({
691
+ file: filePath,
692
+ line: 1,
693
+ column: 1,
694
+ severity: 'warning',
695
+ message: `Value at ${path} below minimum: ${data} < ${schema.minimum}`,
696
+ rule: 'below-minimum',
697
+ analyzer: 'schema'
698
+ });
699
+ }
700
+ if (schema.maximum !== undefined && data > schema.maximum) {
701
+ violations.push({
702
+ file: filePath,
703
+ line: 1,
704
+ column: 1,
705
+ severity: 'warning',
706
+ message: `Value at ${path} above maximum: ${data} > ${schema.maximum}`,
707
+ rule: 'above-maximum',
708
+ analyzer: 'schema'
709
+ });
710
+ }
711
+ }
712
+ // Array validations
713
+ if (schema.type === 'array' && Array.isArray(data)) {
714
+ if (schema.minItems !== undefined && data.length < schema.minItems) {
715
+ violations.push({
716
+ file: filePath,
717
+ line: 1,
718
+ column: 1,
719
+ severity: 'warning',
720
+ message: `Array at ${path} has too few items: ${data.length} < ${schema.minItems}`,
721
+ rule: 'too-few-items',
722
+ analyzer: 'schema'
723
+ });
724
+ }
725
+ if (schema.maxItems !== undefined && data.length > schema.maxItems) {
726
+ violations.push({
727
+ file: filePath,
728
+ line: 1,
729
+ column: 1,
730
+ severity: 'warning',
731
+ message: `Array at ${path} has too many items: ${data.length} > ${schema.maxItems}`,
732
+ rule: 'too-many-items',
733
+ analyzer: 'schema'
734
+ });
735
+ }
736
+ if (schema.items) {
737
+ data.forEach((item, index) => {
738
+ this.validateAgainstSchema(item, schema.items, filePath, violations, config, `${path}[${index}]`);
739
+ });
740
+ }
741
+ }
742
+ // Object validations
743
+ if (schema.type === 'object' && typeof data === 'object' && data !== null) {
744
+ // Check required fields
745
+ if (schema.required && Array.isArray(schema.required)) {
746
+ for (const requiredField of schema.required) {
747
+ if (!(requiredField in data)) {
748
+ violations.push({
749
+ file: filePath,
750
+ line: 1,
751
+ column: 1,
752
+ severity: 'critical',
753
+ message: `Missing required field "${requiredField}" at ${path}`,
754
+ rule: 'missing-required-field',
755
+ analyzer: 'schema'
756
+ });
757
+ }
758
+ }
759
+ }
760
+ // Validate properties
761
+ if (schema.properties) {
762
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
763
+ if (key in data) {
764
+ this.validateAgainstSchema(data[key], propSchema, filePath, violations, config, path ? `${path}.${key}` : key);
765
+ }
766
+ }
767
+ }
768
+ // Check additional properties
769
+ if (schema.additionalProperties === false || (config.strictMode && !schema.additionalProperties)) {
770
+ const definedKeys = new Set(Object.keys(schema.properties || {}));
771
+ const actualKeys = Object.keys(data);
772
+ for (const key of actualKeys) {
773
+ if (!definedKeys.has(key)) {
774
+ violations.push({
775
+ file: filePath,
776
+ line: 1,
777
+ column: 1,
778
+ severity: config.strictMode ? 'critical' : 'warning',
779
+ message: `Unexpected property "${key}" at ${path}`,
780
+ rule: 'unexpected-property',
781
+ analyzer: 'schema'
782
+ });
783
+ }
784
+ }
785
+ }
786
+ else if (typeof schema.additionalProperties === 'object') {
787
+ // Validate additional properties against schema
788
+ const definedKeys = new Set(Object.keys(schema.properties || {}));
789
+ for (const [key, value] of Object.entries(data)) {
790
+ if (!definedKeys.has(key)) {
791
+ this.validateAgainstSchema(value, schema.additionalProperties, filePath, violations, config, path ? `${path}.${key}` : key);
792
+ }
793
+ }
794
+ }
795
+ }
796
+ // Enum validation
797
+ if (schema.enum && Array.isArray(schema.enum)) {
798
+ if (!schema.enum.includes(data)) {
799
+ violations.push({
800
+ file: filePath,
801
+ line: 1,
802
+ column: 1,
803
+ severity: 'critical',
804
+ message: `Value at ${path} not in enum: ${JSON.stringify(data)}. Allowed: ${schema.enum.join(', ')}`,
805
+ rule: 'enum-mismatch',
806
+ analyzer: 'schema'
807
+ });
808
+ }
809
+ }
810
+ }
811
+ }
812
+ //# sourceMappingURL=UniversalSchemaAnalyzer.js.map