deflake 1.2.33 → 1.2.35

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 (2) hide show
  1. package/cli.js +37 -26
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -238,6 +238,18 @@ async function applyFix(res, loc) {
238
238
  continue;
239
239
  }
240
240
  if (p.action === 'INSERT_AFTER') {
241
+ // SAFETY: Detect if we'd insert at class scope (outside any method)
242
+ // Pattern: line is '}' (end of constructor/method) and next non-blank line starts a new method
243
+ const targetLine = lines[idx].trim();
244
+ const nextMeaningful = lines.slice(p.line).find(l => l.trim() !== '');
245
+ const isClassScope = targetLine === '}' && nextMeaningful &&
246
+ /^\s*(async\s+)?\w+\s*\(|^\s*(private|public|protected|static|readonly|get |set )|^\s*}/.test(nextMeaningful);
247
+
248
+ if (isClassScope && /await |return |const |let |var /.test(p.new_line)) {
249
+ console.log(` ${C.YELLOW}⏭️ Blocked: would insert executable code at class scope (after ${targetLine})${C.RESET}`);
250
+ continue;
251
+ }
252
+
241
253
  lines.splice(p.line, 0, cleanLine);
242
254
  console.log(` ${C.GREEN}+ Line ${p.line + 1}: ${cleanLine.trim()}${C.RESET}`);
243
255
  } else {
@@ -294,36 +306,35 @@ function extractLoc(text) {
294
306
  if (!text) return null;
295
307
 
296
308
  // === MULTI-FRAMEWORK LOCATION EXTRACTION ===
297
- // Supports: Playwright, Cypress, WebdriverIO, Jest, Mocha
298
-
299
- // Strategy 1: Absolute path inside parentheses (most precise)
300
- // Format: at FunctionName (/absolute/path/file.ts:75:35)
301
- let m = text.match(/\(([^()]*?[\\/][^()]+\.(?:ts|js|tsx|jsx|mjs|cjs)):(\d+)(?::(\d+))?\)/);
302
- if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
309
+ // Returns the LAST match (closest to the Error Context anchor)
310
+ // to avoid picking up stack traces from previous error blocks.
303
311
 
304
- // Strategy 2: Absolute path after "at" keyword (no parens)
305
- // Format: at /absolute/path/file.ts:75:35
306
- m = text.match(/at\s+(\/[^\s]+\.(?:ts|js|tsx|jsx)):(\d+)(?::(\d+))?/);
307
- if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
308
-
309
- // Strategy 3: Relative path after "at" keyword
310
- // Format: at ../pages/file.ts:75
311
- m = text.match(/at\s+(\.\.?\/[^\s]+\.(?:ts|js|tsx|jsx)):(\d+)(?::(\d+))?/);
312
- if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
312
+ const patterns = [
313
+ // Strategy 1: Absolute path in parens — at Func (/path/file.ts:75:35)
314
+ /\(([^()]*?[\\/][^()]+\.(?:ts|js|tsx|jsx|mjs|cjs)):(\d+)(?::(\d+))?\)/g,
315
+ // Strategy 2: Absolute path — at /path/file.ts:75:35
316
+ /at\s+(\/[^\s]+\.(?:ts|js|tsx|jsx)):(\d+)(?::(\d+))?/g,
317
+ // Strategy 3: Relative path at ../pages/file.ts:75
318
+ /at\s+(\.\.?\/[^\s]+\.(?:ts|js|tsx|jsx)):(\d+)(?::(\d+))?/g,
319
+ ];
313
320
 
314
- // Strategy 4: Playwright "at file:line" in stack (single line match)
315
- m = text.match(/^\s+at\s+(.*?\.(?:ts|js)):(\d+)$/m);
316
- if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
317
-
318
- // Strategy 5: Cypress at Context.<anonymous> (path)
319
- m = text.match(/Context\.<anonymous>\s*\(([^)]+\.(?:ts|js|cy\.ts|cy\.js)):(\d+):(\d+)\)/);
320
- if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
321
+ let lastMatch = null;
322
+ for (const pattern of patterns) {
323
+ let m;
324
+ while ((m = pattern.exec(text)) !== null) {
325
+ lastMatch = { path: path.resolve(m[1]), line: parseInt(m[2]) };
326
+ }
327
+ if (lastMatch) return lastMatch;
328
+ }
321
329
 
322
- // Strategy 6: Generic — any path-like string with /dir/file.ts:line
323
- m = text.match(/((?:[\w@.-]+\/)+[\w.-]+\.(?:ts|js|tsx|jsx)):(\d+)/);
324
- if (m) return { path: path.resolve(m[1]), line: parseInt(m[2]) };
330
+ // Strategy 4: Generic — any path-like /dir/file.ts:line
331
+ const generic = /((?:[\w@.-]+\/)+[\w.-]+\.(?:ts|js|tsx|jsx)):(\d+)/g;
332
+ let m;
333
+ while ((m = generic.exec(text)) !== null) {
334
+ lastMatch = { path: path.resolve(m[1]), line: parseInt(m[2]) };
335
+ }
325
336
 
326
- return null;
337
+ return lastMatch;
327
338
  }
328
339
 
329
340
  async function runDoctor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deflake",
3
- "version": "1.2.33",
3
+ "version": "1.2.35",
4
4
  "description": "AI-powered self-healing tool for Playwright, Cypress, and WebdriverIO tests.",
5
5
  "main": "client.js",
6
6
  "bin": {