driftdetect-core 0.1.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 (221) hide show
  1. package/dist/analyzers/ast-analyzer.d.ts +251 -0
  2. package/dist/analyzers/ast-analyzer.d.ts.map +1 -0
  3. package/dist/analyzers/ast-analyzer.js +548 -0
  4. package/dist/analyzers/ast-analyzer.js.map +1 -0
  5. package/dist/analyzers/flow-analyzer.d.ts +241 -0
  6. package/dist/analyzers/flow-analyzer.d.ts.map +1 -0
  7. package/dist/analyzers/flow-analyzer.js +1219 -0
  8. package/dist/analyzers/flow-analyzer.js.map +1 -0
  9. package/dist/analyzers/index.d.ts +18 -0
  10. package/dist/analyzers/index.d.ts.map +1 -0
  11. package/dist/analyzers/index.js +19 -0
  12. package/dist/analyzers/index.js.map +1 -0
  13. package/dist/analyzers/semantic-analyzer.d.ts +252 -0
  14. package/dist/analyzers/semantic-analyzer.d.ts.map +1 -0
  15. package/dist/analyzers/semantic-analyzer.js +1182 -0
  16. package/dist/analyzers/semantic-analyzer.js.map +1 -0
  17. package/dist/analyzers/type-analyzer.d.ts +289 -0
  18. package/dist/analyzers/type-analyzer.d.ts.map +1 -0
  19. package/dist/analyzers/type-analyzer.js +1269 -0
  20. package/dist/analyzers/type-analyzer.js.map +1 -0
  21. package/dist/analyzers/types.d.ts +537 -0
  22. package/dist/analyzers/types.d.ts.map +1 -0
  23. package/dist/analyzers/types.js +11 -0
  24. package/dist/analyzers/types.js.map +1 -0
  25. package/dist/config/config-loader.d.ts +166 -0
  26. package/dist/config/config-loader.d.ts.map +1 -0
  27. package/dist/config/config-loader.js +429 -0
  28. package/dist/config/config-loader.js.map +1 -0
  29. package/dist/config/config-validator.d.ts +204 -0
  30. package/dist/config/config-validator.d.ts.map +1 -0
  31. package/dist/config/config-validator.js +632 -0
  32. package/dist/config/config-validator.js.map +1 -0
  33. package/dist/config/defaults.d.ts +8 -0
  34. package/dist/config/defaults.d.ts.map +1 -0
  35. package/dist/config/defaults.js +26 -0
  36. package/dist/config/defaults.js.map +1 -0
  37. package/dist/config/index.d.ts +10 -0
  38. package/dist/config/index.d.ts.map +1 -0
  39. package/dist/config/index.js +10 -0
  40. package/dist/config/index.js.map +1 -0
  41. package/dist/config/types.d.ts +47 -0
  42. package/dist/config/types.d.ts.map +1 -0
  43. package/dist/config/types.js +7 -0
  44. package/dist/config/types.js.map +1 -0
  45. package/dist/index.d.ts +37 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +39 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/manifest/exporter.d.ts +21 -0
  50. package/dist/manifest/exporter.d.ts.map +1 -0
  51. package/dist/manifest/exporter.js +339 -0
  52. package/dist/manifest/exporter.js.map +1 -0
  53. package/dist/manifest/index.d.ts +14 -0
  54. package/dist/manifest/index.d.ts.map +1 -0
  55. package/dist/manifest/index.js +15 -0
  56. package/dist/manifest/index.js.map +1 -0
  57. package/dist/manifest/manifest-store.d.ts +111 -0
  58. package/dist/manifest/manifest-store.d.ts.map +1 -0
  59. package/dist/manifest/manifest-store.js +418 -0
  60. package/dist/manifest/manifest-store.js.map +1 -0
  61. package/dist/manifest/types.d.ts +238 -0
  62. package/dist/manifest/types.d.ts.map +1 -0
  63. package/dist/manifest/types.js +11 -0
  64. package/dist/manifest/types.js.map +1 -0
  65. package/dist/matcher/confidence-scorer.d.ts +188 -0
  66. package/dist/matcher/confidence-scorer.d.ts.map +1 -0
  67. package/dist/matcher/confidence-scorer.js +302 -0
  68. package/dist/matcher/confidence-scorer.js.map +1 -0
  69. package/dist/matcher/index.d.ts +24 -0
  70. package/dist/matcher/index.d.ts.map +1 -0
  71. package/dist/matcher/index.js +26 -0
  72. package/dist/matcher/index.js.map +1 -0
  73. package/dist/matcher/outlier-detector.d.ts +252 -0
  74. package/dist/matcher/outlier-detector.d.ts.map +1 -0
  75. package/dist/matcher/outlier-detector.js +544 -0
  76. package/dist/matcher/outlier-detector.js.map +1 -0
  77. package/dist/matcher/pattern-matcher.d.ts +169 -0
  78. package/dist/matcher/pattern-matcher.d.ts.map +1 -0
  79. package/dist/matcher/pattern-matcher.js +692 -0
  80. package/dist/matcher/pattern-matcher.js.map +1 -0
  81. package/dist/matcher/types.d.ts +476 -0
  82. package/dist/matcher/types.d.ts.map +1 -0
  83. package/dist/matcher/types.js +36 -0
  84. package/dist/matcher/types.js.map +1 -0
  85. package/dist/parsers/base-parser.d.ts +282 -0
  86. package/dist/parsers/base-parser.d.ts.map +1 -0
  87. package/dist/parsers/base-parser.js +421 -0
  88. package/dist/parsers/base-parser.js.map +1 -0
  89. package/dist/parsers/css-parser.d.ts +225 -0
  90. package/dist/parsers/css-parser.d.ts.map +1 -0
  91. package/dist/parsers/css-parser.js +477 -0
  92. package/dist/parsers/css-parser.js.map +1 -0
  93. package/dist/parsers/index.d.ts +15 -0
  94. package/dist/parsers/index.d.ts.map +1 -0
  95. package/dist/parsers/index.js +15 -0
  96. package/dist/parsers/index.js.map +1 -0
  97. package/dist/parsers/json-parser.d.ts +219 -0
  98. package/dist/parsers/json-parser.d.ts.map +1 -0
  99. package/dist/parsers/json-parser.js +602 -0
  100. package/dist/parsers/json-parser.js.map +1 -0
  101. package/dist/parsers/markdown-parser.d.ts +276 -0
  102. package/dist/parsers/markdown-parser.d.ts.map +1 -0
  103. package/dist/parsers/markdown-parser.js +731 -0
  104. package/dist/parsers/markdown-parser.js.map +1 -0
  105. package/dist/parsers/parser-manager.d.ts +294 -0
  106. package/dist/parsers/parser-manager.d.ts.map +1 -0
  107. package/dist/parsers/parser-manager.js +738 -0
  108. package/dist/parsers/parser-manager.js.map +1 -0
  109. package/dist/parsers/python-parser.d.ts +204 -0
  110. package/dist/parsers/python-parser.d.ts.map +1 -0
  111. package/dist/parsers/python-parser.js +517 -0
  112. package/dist/parsers/python-parser.js.map +1 -0
  113. package/dist/parsers/types.d.ts +43 -0
  114. package/dist/parsers/types.d.ts.map +1 -0
  115. package/dist/parsers/types.js +7 -0
  116. package/dist/parsers/types.js.map +1 -0
  117. package/dist/parsers/typescript-parser.d.ts +264 -0
  118. package/dist/parsers/typescript-parser.d.ts.map +1 -0
  119. package/dist/parsers/typescript-parser.js +658 -0
  120. package/dist/parsers/typescript-parser.js.map +1 -0
  121. package/dist/rules/evaluator.d.ts +305 -0
  122. package/dist/rules/evaluator.d.ts.map +1 -0
  123. package/dist/rules/evaluator.js +579 -0
  124. package/dist/rules/evaluator.js.map +1 -0
  125. package/dist/rules/index.d.ts +13 -0
  126. package/dist/rules/index.d.ts.map +1 -0
  127. package/dist/rules/index.js +13 -0
  128. package/dist/rules/index.js.map +1 -0
  129. package/dist/rules/quick-fix-generator.d.ts +334 -0
  130. package/dist/rules/quick-fix-generator.d.ts.map +1 -0
  131. package/dist/rules/quick-fix-generator.js +1075 -0
  132. package/dist/rules/quick-fix-generator.js.map +1 -0
  133. package/dist/rules/rule-engine.d.ts +241 -0
  134. package/dist/rules/rule-engine.d.ts.map +1 -0
  135. package/dist/rules/rule-engine.js +585 -0
  136. package/dist/rules/rule-engine.js.map +1 -0
  137. package/dist/rules/severity-manager.d.ts +394 -0
  138. package/dist/rules/severity-manager.d.ts.map +1 -0
  139. package/dist/rules/severity-manager.js +619 -0
  140. package/dist/rules/severity-manager.js.map +1 -0
  141. package/dist/rules/types.d.ts +370 -0
  142. package/dist/rules/types.d.ts.map +1 -0
  143. package/dist/rules/types.js +133 -0
  144. package/dist/rules/types.js.map +1 -0
  145. package/dist/rules/variant-manager.d.ts +388 -0
  146. package/dist/rules/variant-manager.d.ts.map +1 -0
  147. package/dist/rules/variant-manager.js +777 -0
  148. package/dist/rules/variant-manager.js.map +1 -0
  149. package/dist/scanner/change-detector.d.ts +164 -0
  150. package/dist/scanner/change-detector.d.ts.map +1 -0
  151. package/dist/scanner/change-detector.js +263 -0
  152. package/dist/scanner/change-detector.js.map +1 -0
  153. package/dist/scanner/dependency-graph.d.ts +270 -0
  154. package/dist/scanner/dependency-graph.d.ts.map +1 -0
  155. package/dist/scanner/dependency-graph.js +436 -0
  156. package/dist/scanner/dependency-graph.js.map +1 -0
  157. package/dist/scanner/file-walker.d.ts +127 -0
  158. package/dist/scanner/file-walker.d.ts.map +1 -0
  159. package/dist/scanner/file-walker.js +526 -0
  160. package/dist/scanner/file-walker.js.map +1 -0
  161. package/dist/scanner/index.d.ts +12 -0
  162. package/dist/scanner/index.d.ts.map +1 -0
  163. package/dist/scanner/index.js +12 -0
  164. package/dist/scanner/index.js.map +1 -0
  165. package/dist/scanner/types.d.ts +218 -0
  166. package/dist/scanner/types.d.ts.map +1 -0
  167. package/dist/scanner/types.js +10 -0
  168. package/dist/scanner/types.js.map +1 -0
  169. package/dist/scanner/worker-pool.d.ts +317 -0
  170. package/dist/scanner/worker-pool.d.ts.map +1 -0
  171. package/dist/scanner/worker-pool.js +571 -0
  172. package/dist/scanner/worker-pool.js.map +1 -0
  173. package/dist/store/cache-manager.d.ts +179 -0
  174. package/dist/store/cache-manager.d.ts.map +1 -0
  175. package/dist/store/cache-manager.js +391 -0
  176. package/dist/store/cache-manager.js.map +1 -0
  177. package/dist/store/history-store.d.ts +314 -0
  178. package/dist/store/history-store.d.ts.map +1 -0
  179. package/dist/store/history-store.js +707 -0
  180. package/dist/store/history-store.js.map +1 -0
  181. package/dist/store/index.d.ts +20 -0
  182. package/dist/store/index.d.ts.map +1 -0
  183. package/dist/store/index.js +26 -0
  184. package/dist/store/index.js.map +1 -0
  185. package/dist/store/lock-file-manager.d.ts +202 -0
  186. package/dist/store/lock-file-manager.d.ts.map +1 -0
  187. package/dist/store/lock-file-manager.js +475 -0
  188. package/dist/store/lock-file-manager.js.map +1 -0
  189. package/dist/store/pattern-store.d.ts +289 -0
  190. package/dist/store/pattern-store.d.ts.map +1 -0
  191. package/dist/store/pattern-store.js +936 -0
  192. package/dist/store/pattern-store.js.map +1 -0
  193. package/dist/store/schema-validator.d.ts +159 -0
  194. package/dist/store/schema-validator.d.ts.map +1 -0
  195. package/dist/store/schema-validator.js +1096 -0
  196. package/dist/store/schema-validator.js.map +1 -0
  197. package/dist/store/types.d.ts +585 -0
  198. package/dist/store/types.d.ts.map +1 -0
  199. package/dist/store/types.js +82 -0
  200. package/dist/store/types.js.map +1 -0
  201. package/dist/types/analysis.d.ts +19 -0
  202. package/dist/types/analysis.d.ts.map +1 -0
  203. package/dist/types/analysis.js +5 -0
  204. package/dist/types/analysis.js.map +1 -0
  205. package/dist/types/common.d.ts +7 -0
  206. package/dist/types/common.d.ts.map +1 -0
  207. package/dist/types/common.js +5 -0
  208. package/dist/types/common.js.map +1 -0
  209. package/dist/types/index.d.ts +12 -0
  210. package/dist/types/index.d.ts.map +1 -0
  211. package/dist/types/index.js +10 -0
  212. package/dist/types/index.js.map +1 -0
  213. package/dist/types/patterns.d.ts +40 -0
  214. package/dist/types/patterns.d.ts.map +1 -0
  215. package/dist/types/patterns.js +7 -0
  216. package/dist/types/patterns.js.map +1 -0
  217. package/dist/types/violations.d.ts +7 -0
  218. package/dist/types/violations.d.ts.map +1 -0
  219. package/dist/types/violations.js +7 -0
  220. package/dist/types/violations.js.map +1 -0
  221. package/package.json +46 -0
@@ -0,0 +1,692 @@
1
+ /**
2
+ * Pattern Matcher - Match code against pattern definitions
3
+ *
4
+ * Provides pattern matching capabilities using AST-based, regex-based,
5
+ * and structural matching methods. Supports caching of match results
6
+ * for improved performance.
7
+ *
8
+ * @requirements 5.1 - Pattern matching with confidence scoring
9
+ */
10
+ /**
11
+ * PatternMatcher class for matching code against pattern definitions.
12
+ *
13
+ * Supports multiple matching methods:
14
+ * - AST-based matching: Uses AST node types and properties
15
+ * - Regex-based matching: Uses regular expressions
16
+ * - Structural matching: Uses file paths and naming conventions
17
+ *
18
+ * @requirements 5.1 - Pattern matching with confidence scoring
19
+ */
20
+ export class PatternMatcher {
21
+ config;
22
+ cache;
23
+ cacheMaxSize;
24
+ cacheTTL;
25
+ /**
26
+ * Create a new PatternMatcher instance.
27
+ *
28
+ * @param config - Matcher configuration options
29
+ */
30
+ constructor(config = {}) {
31
+ this.config = config;
32
+ this.cache = new Map();
33
+ this.cacheMaxSize = config.cache?.maxSize ?? 1000;
34
+ this.cacheTTL = config.cache?.ttl ?? 60000; // 1 minute default
35
+ }
36
+ /**
37
+ * Match code against a single pattern definition.
38
+ *
39
+ * @param context - The matcher context containing file info and AST
40
+ * @param pattern - The pattern definition to match against
41
+ * @param options - Optional matching options
42
+ * @returns Array of pattern match results
43
+ */
44
+ match(context, pattern, options = {}) {
45
+ // Check if pattern is enabled
46
+ if (!pattern.enabled) {
47
+ return [];
48
+ }
49
+ // Check language filter
50
+ if (pattern.languages && pattern.languages.length > 0) {
51
+ if (!pattern.languages.includes(context.language)) {
52
+ return [];
53
+ }
54
+ }
55
+ // Check file include/exclude patterns
56
+ if (!this.matchesFilePatterns(context.file, pattern)) {
57
+ return [];
58
+ }
59
+ // Check cache if enabled
60
+ const useCache = options.useCache ?? this.config.cache?.enabled ?? true;
61
+ if (useCache) {
62
+ const cached = this.getCachedResults(context, pattern);
63
+ if (cached) {
64
+ return this.filterResults(cached, options);
65
+ }
66
+ }
67
+ // Perform matching based on match type
68
+ let results;
69
+ try {
70
+ switch (pattern.matchType) {
71
+ case 'ast':
72
+ results = this.matchAST(context, pattern);
73
+ break;
74
+ case 'regex':
75
+ results = this.matchRegex(context, pattern);
76
+ break;
77
+ case 'structural':
78
+ results = this.matchStructural(context, pattern);
79
+ break;
80
+ case 'semantic':
81
+ // Semantic matching falls back to AST for now
82
+ results = this.matchAST(context, pattern);
83
+ break;
84
+ case 'custom':
85
+ // Custom matching not implemented yet
86
+ results = [];
87
+ break;
88
+ default:
89
+ results = [];
90
+ }
91
+ }
92
+ catch (error) {
93
+ // Handle errors gracefully
94
+ console.error(`Error matching pattern ${pattern.id}:`, error);
95
+ results = [];
96
+ }
97
+ // Cache results
98
+ if (useCache) {
99
+ this.cacheResults(context, pattern, results);
100
+ }
101
+ return this.filterResults(results, options);
102
+ }
103
+ /**
104
+ * Match code against multiple pattern definitions.
105
+ *
106
+ * @param context - The matcher context containing file info and AST
107
+ * @param patterns - Array of pattern definitions to match against
108
+ * @param options - Optional matching options
109
+ * @returns Matching result with all matches and errors
110
+ */
111
+ matchAll(context, patterns, options = {}) {
112
+ const startTime = Date.now();
113
+ const allMatches = [];
114
+ const errors = [];
115
+ for (const pattern of patterns) {
116
+ try {
117
+ const matches = this.match(context, pattern, options);
118
+ allMatches.push(...matches);
119
+ }
120
+ catch (error) {
121
+ errors.push({
122
+ message: error instanceof Error ? error.message : String(error),
123
+ patternId: pattern.id,
124
+ recoverable: true,
125
+ });
126
+ }
127
+ }
128
+ return {
129
+ file: context.file,
130
+ matches: allMatches,
131
+ outliers: [], // Outlier detection is handled by OutlierDetector
132
+ duration: Date.now() - startTime,
133
+ success: errors.length === 0,
134
+ errors,
135
+ };
136
+ }
137
+ /**
138
+ * Clear the match cache.
139
+ */
140
+ clearCache() {
141
+ this.cache.clear();
142
+ }
143
+ /**
144
+ * Get cache statistics.
145
+ */
146
+ getCacheStats() {
147
+ return {
148
+ size: this.cache.size,
149
+ maxSize: this.cacheMaxSize,
150
+ };
151
+ }
152
+ // ============================================================================
153
+ // AST-based Matching
154
+ // ============================================================================
155
+ /**
156
+ * Perform AST-based pattern matching.
157
+ */
158
+ matchAST(context, pattern) {
159
+ if (!context.ast) {
160
+ return [];
161
+ }
162
+ const config = pattern.astConfig;
163
+ if (!config) {
164
+ return [];
165
+ }
166
+ const results = [];
167
+ this.traverseAST(context.ast.rootNode, (node, depth) => {
168
+ // Check depth constraints
169
+ if (config.minDepth !== undefined && depth < config.minDepth) {
170
+ return;
171
+ }
172
+ if (config.maxDepth !== undefined && depth > config.maxDepth) {
173
+ return;
174
+ }
175
+ const matchResult = this.matchASTNode(node, config);
176
+ if (matchResult.matches) {
177
+ const result = {
178
+ patternId: pattern.id,
179
+ location: this.nodeToLocation(node, context.file),
180
+ confidence: matchResult.confidence,
181
+ isOutlier: false,
182
+ matchType: 'ast',
183
+ timestamp: new Date(),
184
+ };
185
+ if (this.config.includeAstNodes) {
186
+ result.matchedNode = node;
187
+ }
188
+ if (this.config.includeMatchedText) {
189
+ result.matchedText = node.text;
190
+ }
191
+ results.push(result);
192
+ }
193
+ });
194
+ return results;
195
+ }
196
+ /**
197
+ * Match a single AST node against an AST match config.
198
+ */
199
+ matchASTNode(node, config) {
200
+ let confidence = 1.0;
201
+ let matchCount = 0;
202
+ let totalChecks = 0;
203
+ // Check node type
204
+ if (config.nodeType) {
205
+ totalChecks++;
206
+ if (node.type === config.nodeType) {
207
+ matchCount++;
208
+ }
209
+ else {
210
+ return { matches: false, confidence: 0 };
211
+ }
212
+ }
213
+ // Check properties
214
+ if (config.properties) {
215
+ for (const [key, value] of Object.entries(config.properties)) {
216
+ totalChecks++;
217
+ const nodeValue = node[key];
218
+ if (this.matchPropertyValue(nodeValue, value)) {
219
+ matchCount++;
220
+ }
221
+ else {
222
+ return { matches: false, confidence: 0 };
223
+ }
224
+ }
225
+ }
226
+ // Check children patterns
227
+ if (config.children && config.children.length > 0) {
228
+ const childResults = this.matchASTChildren(node, config.children, config.matchDescendants);
229
+ if (!childResults.matches) {
230
+ return { matches: false, confidence: 0 };
231
+ }
232
+ confidence *= childResults.confidence;
233
+ }
234
+ // Calculate final confidence
235
+ if (totalChecks > 0) {
236
+ confidence *= matchCount / totalChecks;
237
+ }
238
+ return { matches: true, confidence };
239
+ }
240
+ /**
241
+ * Match AST children against child patterns.
242
+ */
243
+ matchASTChildren(node, childPatterns, matchDescendants) {
244
+ const nodesToSearch = matchDescendants
245
+ ? this.getAllDescendants(node)
246
+ : node.children;
247
+ let matchedCount = 0;
248
+ for (const childPattern of childPatterns) {
249
+ let found = false;
250
+ for (const child of nodesToSearch) {
251
+ const result = this.matchASTNode(child, childPattern);
252
+ if (result.matches) {
253
+ found = true;
254
+ matchedCount++;
255
+ break;
256
+ }
257
+ }
258
+ if (!found) {
259
+ return { matches: false, confidence: 0 };
260
+ }
261
+ }
262
+ return {
263
+ matches: true,
264
+ confidence: matchedCount / childPatterns.length,
265
+ };
266
+ }
267
+ /**
268
+ * Get all descendants of an AST node.
269
+ */
270
+ getAllDescendants(node) {
271
+ const descendants = [];
272
+ const collect = (n) => {
273
+ for (const child of n.children) {
274
+ descendants.push(child);
275
+ collect(child);
276
+ }
277
+ };
278
+ collect(node);
279
+ return descendants;
280
+ }
281
+ /**
282
+ * Traverse AST depth-first.
283
+ */
284
+ traverseAST(node, visitor, depth = 0) {
285
+ visitor(node, depth);
286
+ for (const child of node.children) {
287
+ this.traverseAST(child, visitor, depth + 1);
288
+ }
289
+ }
290
+ /**
291
+ * Match a property value against an expected value.
292
+ */
293
+ matchPropertyValue(actual, expected) {
294
+ if (expected instanceof RegExp) {
295
+ return typeof actual === 'string' && expected.test(actual);
296
+ }
297
+ if (typeof expected === 'object' && expected !== null) {
298
+ if (typeof actual !== 'object' || actual === null) {
299
+ return false;
300
+ }
301
+ // Deep comparison for objects
302
+ return JSON.stringify(actual) === JSON.stringify(expected);
303
+ }
304
+ return actual === expected;
305
+ }
306
+ // ============================================================================
307
+ // Regex-based Matching
308
+ // ============================================================================
309
+ /**
310
+ * Perform regex-based pattern matching.
311
+ */
312
+ matchRegex(context, pattern) {
313
+ const config = pattern.regexConfig;
314
+ if (!config) {
315
+ return [];
316
+ }
317
+ const results = [];
318
+ const regex = this.createRegex(config);
319
+ if (!regex) {
320
+ return [];
321
+ }
322
+ const lines = context.content.split('\n');
323
+ let match;
324
+ // Reset regex state for global matching
325
+ regex.lastIndex = 0;
326
+ while ((match = regex.exec(context.content)) !== null) {
327
+ const location = this.indexToLocation(match.index, lines, context.file);
328
+ const captures = this.extractCaptures(match, config.captureGroups);
329
+ const result = {
330
+ patternId: pattern.id,
331
+ location,
332
+ confidence: 1.0, // Regex matches are binary
333
+ isOutlier: false,
334
+ matchType: 'regex',
335
+ timestamp: new Date(),
336
+ };
337
+ if (this.config.includeMatchedText) {
338
+ result.matchedText = match[0];
339
+ }
340
+ if (captures) {
341
+ result.captures = captures;
342
+ }
343
+ results.push(result);
344
+ // Prevent infinite loop for zero-length matches
345
+ if (match[0].length === 0) {
346
+ regex.lastIndex++;
347
+ }
348
+ }
349
+ return results;
350
+ }
351
+ /**
352
+ * Create a RegExp from regex config.
353
+ */
354
+ createRegex(config) {
355
+ try {
356
+ let flags = config.flags ?? 'g';
357
+ if (!flags.includes('g')) {
358
+ flags += 'g'; // Always use global flag for multiple matches
359
+ }
360
+ if (config.multiline && !flags.includes('m')) {
361
+ flags += 'm';
362
+ }
363
+ return new RegExp(config.pattern, flags);
364
+ }
365
+ catch {
366
+ return null;
367
+ }
368
+ }
369
+ /**
370
+ * Extract named captures from a regex match.
371
+ */
372
+ extractCaptures(match, captureGroups) {
373
+ if (!captureGroups || captureGroups.length === 0) {
374
+ return undefined;
375
+ }
376
+ const captures = {};
377
+ for (let i = 0; i < captureGroups.length; i++) {
378
+ const groupName = captureGroups[i];
379
+ const groupValue = match[i + 1]; // Groups start at index 1
380
+ if (groupName && groupValue !== undefined) {
381
+ captures[groupName] = groupValue;
382
+ }
383
+ }
384
+ return Object.keys(captures).length > 0 ? captures : undefined;
385
+ }
386
+ /**
387
+ * Convert a string index to a location.
388
+ */
389
+ indexToLocation(index, lines, file) {
390
+ let currentIndex = 0;
391
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
392
+ const line = lines[lineNum];
393
+ if (line === undefined)
394
+ continue;
395
+ const lineLength = line.length + 1; // +1 for newline
396
+ if (currentIndex + lineLength > index) {
397
+ return {
398
+ file,
399
+ line: lineNum + 1, // 1-indexed
400
+ column: index - currentIndex + 1, // 1-indexed
401
+ };
402
+ }
403
+ currentIndex += lineLength;
404
+ }
405
+ // Default to end of file
406
+ return {
407
+ file,
408
+ line: lines.length,
409
+ column: 1,
410
+ };
411
+ }
412
+ // ============================================================================
413
+ // Structural Matching
414
+ // ============================================================================
415
+ /**
416
+ * Perform structural pattern matching.
417
+ */
418
+ matchStructural(context, pattern) {
419
+ const config = pattern.structuralConfig;
420
+ if (!config) {
421
+ return [];
422
+ }
423
+ const results = [];
424
+ let confidence = 1.0;
425
+ let matchCount = 0;
426
+ let totalChecks = 0;
427
+ // Check path pattern
428
+ if (config.pathPattern) {
429
+ totalChecks++;
430
+ if (this.matchGlobPattern(context.file, config.pathPattern)) {
431
+ matchCount++;
432
+ }
433
+ else {
434
+ return []; // Path pattern is required to match
435
+ }
436
+ }
437
+ // Check directory pattern
438
+ if (config.directoryPattern) {
439
+ totalChecks++;
440
+ const dir = this.getDirectory(context.file);
441
+ if (this.matchGlobPattern(dir, config.directoryPattern)) {
442
+ matchCount++;
443
+ }
444
+ else {
445
+ return [];
446
+ }
447
+ }
448
+ // Check naming pattern
449
+ if (config.namingPattern) {
450
+ totalChecks++;
451
+ const fileName = this.getFileName(context.file);
452
+ if (this.matchNamingPattern(fileName, config.namingPattern)) {
453
+ matchCount++;
454
+ }
455
+ else {
456
+ return [];
457
+ }
458
+ }
459
+ // Check extension
460
+ if (config.extension) {
461
+ totalChecks++;
462
+ const ext = this.getExtension(context.file);
463
+ if (ext === config.extension || ext === `.${config.extension}`) {
464
+ matchCount++;
465
+ }
466
+ else {
467
+ return [];
468
+ }
469
+ }
470
+ // Calculate confidence
471
+ if (totalChecks > 0) {
472
+ confidence = matchCount / totalChecks;
473
+ }
474
+ // If all checks passed, add a match for the file
475
+ if (matchCount === totalChecks && totalChecks > 0) {
476
+ results.push({
477
+ patternId: pattern.id,
478
+ location: {
479
+ file: context.file,
480
+ line: 1,
481
+ column: 1,
482
+ },
483
+ confidence,
484
+ isOutlier: false,
485
+ matchType: 'structural',
486
+ timestamp: new Date(),
487
+ });
488
+ }
489
+ return results;
490
+ }
491
+ /**
492
+ * Match a path against a glob pattern.
493
+ */
494
+ matchGlobPattern(path, pattern) {
495
+ // Convert glob pattern to regex
496
+ const regexPattern = pattern
497
+ .replace(/\./g, '\\.')
498
+ .replace(/\*\*/g, '{{GLOBSTAR}}')
499
+ .replace(/\*/g, '[^/]*')
500
+ .replace(/\?/g, '[^/]')
501
+ .replace(/{{GLOBSTAR}}/g, '.*');
502
+ const regex = new RegExp(`^${regexPattern}$`);
503
+ return regex.test(path);
504
+ }
505
+ /**
506
+ * Match a filename against a naming pattern.
507
+ */
508
+ matchNamingPattern(fileName, pattern) {
509
+ // Support common naming conventions
510
+ switch (pattern.toLowerCase()) {
511
+ case 'pascalcase':
512
+ return /^[A-Z][a-zA-Z0-9]*$/.test(fileName);
513
+ case 'camelcase':
514
+ return /^[a-z][a-zA-Z0-9]*$/.test(fileName);
515
+ case 'kebab-case':
516
+ return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(fileName);
517
+ case 'snake_case':
518
+ return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(fileName);
519
+ case 'screaming_snake_case':
520
+ return /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/.test(fileName);
521
+ default:
522
+ // Treat as regex pattern
523
+ try {
524
+ const regex = new RegExp(pattern);
525
+ return regex.test(fileName);
526
+ }
527
+ catch {
528
+ return false;
529
+ }
530
+ }
531
+ }
532
+ /**
533
+ * Get the directory part of a path.
534
+ */
535
+ getDirectory(path) {
536
+ const lastSlash = path.lastIndexOf('/');
537
+ return lastSlash >= 0 ? path.substring(0, lastSlash) : '';
538
+ }
539
+ /**
540
+ * Get the filename without extension.
541
+ */
542
+ getFileName(path) {
543
+ const lastSlash = path.lastIndexOf('/');
544
+ const fileName = lastSlash >= 0 ? path.substring(lastSlash + 1) : path;
545
+ const lastDot = fileName.lastIndexOf('.');
546
+ return lastDot >= 0 ? fileName.substring(0, lastDot) : fileName;
547
+ }
548
+ /**
549
+ * Get the file extension.
550
+ */
551
+ getExtension(path) {
552
+ const lastDot = path.lastIndexOf('.');
553
+ return lastDot >= 0 ? path.substring(lastDot) : '';
554
+ }
555
+ // ============================================================================
556
+ // File Pattern Matching
557
+ // ============================================================================
558
+ /**
559
+ * Check if a file matches the pattern's include/exclude patterns.
560
+ */
561
+ matchesFilePatterns(file, pattern) {
562
+ // Check exclude patterns first
563
+ if (pattern.excludePatterns && pattern.excludePatterns.length > 0) {
564
+ for (const excludePattern of pattern.excludePatterns) {
565
+ if (this.matchGlobPattern(file, excludePattern)) {
566
+ return false;
567
+ }
568
+ }
569
+ }
570
+ // Check include patterns
571
+ if (pattern.includePatterns && pattern.includePatterns.length > 0) {
572
+ for (const includePattern of pattern.includePatterns) {
573
+ if (this.matchGlobPattern(file, includePattern)) {
574
+ return true;
575
+ }
576
+ }
577
+ return false; // No include pattern matched
578
+ }
579
+ return true; // No include patterns means include all
580
+ }
581
+ // ============================================================================
582
+ // Caching
583
+ // ============================================================================
584
+ /**
585
+ * Generate a cache key for a context and pattern.
586
+ */
587
+ getCacheKey(context, pattern) {
588
+ return `${context.file}:${pattern.id}`;
589
+ }
590
+ /**
591
+ * Generate a content hash for cache validation.
592
+ */
593
+ getContentHash(content) {
594
+ // Simple hash function for cache validation
595
+ let hash = 0;
596
+ for (let i = 0; i < content.length; i++) {
597
+ const char = content.charCodeAt(i);
598
+ hash = ((hash << 5) - hash) + char;
599
+ hash = hash & hash; // Convert to 32-bit integer
600
+ }
601
+ return hash.toString(16);
602
+ }
603
+ /**
604
+ * Get cached results if available and valid.
605
+ */
606
+ getCachedResults(context, pattern) {
607
+ const key = this.getCacheKey(context, pattern);
608
+ const entry = this.cache.get(key);
609
+ if (!entry) {
610
+ return null;
611
+ }
612
+ // Check TTL
613
+ if (Date.now() - entry.timestamp > this.cacheTTL) {
614
+ this.cache.delete(key);
615
+ return null;
616
+ }
617
+ // Check content hash
618
+ const currentHash = this.getContentHash(context.content);
619
+ if (entry.contentHash !== currentHash) {
620
+ this.cache.delete(key);
621
+ return null;
622
+ }
623
+ return entry.results;
624
+ }
625
+ /**
626
+ * Cache match results.
627
+ */
628
+ cacheResults(context, pattern, results) {
629
+ // Evict old entries if cache is full
630
+ if (this.cache.size >= this.cacheMaxSize) {
631
+ this.evictOldestEntry();
632
+ }
633
+ const key = this.getCacheKey(context, pattern);
634
+ this.cache.set(key, {
635
+ results,
636
+ timestamp: Date.now(),
637
+ contentHash: this.getContentHash(context.content),
638
+ });
639
+ }
640
+ /**
641
+ * Evict the oldest cache entry.
642
+ */
643
+ evictOldestEntry() {
644
+ let oldestKey = null;
645
+ let oldestTime = Infinity;
646
+ for (const [key, entry] of this.cache) {
647
+ if (entry.timestamp < oldestTime) {
648
+ oldestTime = entry.timestamp;
649
+ oldestKey = key;
650
+ }
651
+ }
652
+ if (oldestKey) {
653
+ this.cache.delete(oldestKey);
654
+ }
655
+ }
656
+ // ============================================================================
657
+ // Result Filtering
658
+ // ============================================================================
659
+ /**
660
+ * Filter results based on options.
661
+ */
662
+ filterResults(results, options) {
663
+ let filtered = results;
664
+ // Filter by minimum confidence
665
+ const minConfidence = options.minConfidence ?? this.config.minConfidence ?? 0;
666
+ if (minConfidence > 0) {
667
+ filtered = filtered.filter((r) => r.confidence >= minConfidence);
668
+ }
669
+ // Limit results
670
+ const maxMatches = options.maxMatches ?? this.config.maxMatchesPerPattern;
671
+ if (maxMatches !== undefined && filtered.length > maxMatches) {
672
+ filtered = filtered.slice(0, maxMatches);
673
+ }
674
+ return filtered;
675
+ }
676
+ // ============================================================================
677
+ // Utility Methods
678
+ // ============================================================================
679
+ /**
680
+ * Convert an AST node to a Location.
681
+ */
682
+ nodeToLocation(node, file) {
683
+ return {
684
+ file,
685
+ line: node.startPosition.row + 1, // Convert to 1-indexed
686
+ column: node.startPosition.column + 1, // Convert to 1-indexed
687
+ endLine: node.endPosition.row + 1,
688
+ endColumn: node.endPosition.column + 1,
689
+ };
690
+ }
691
+ }
692
+ //# sourceMappingURL=pattern-matcher.js.map