trickle-observe 0.2.86 → 0.2.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/observe-register.js +91 -1
- package/dist/trace-var.js +5 -2
- package/dist/vite-plugin.js +58 -1
- package/dist-esm/vite-plugin.js +58 -1
- package/package.json +1 -1
- package/src/observe-register.ts +69 -1
- package/src/trace-var.ts +3 -1
- package/src/vite-plugin.ts +41 -1
package/dist/observe-register.js
CHANGED
|
@@ -327,6 +327,33 @@ function findClosingBrace(source, openBrace) {
|
|
|
327
327
|
pos++;
|
|
328
328
|
pos++; // skip past /
|
|
329
329
|
}
|
|
330
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] !== '/' && source[pos + 1] !== '*') {
|
|
331
|
+
// Possible regex literal — check preceding context
|
|
332
|
+
let p = pos - 1;
|
|
333
|
+
while (p >= 0 && (source[p] === ' ' || source[p] === '\t'))
|
|
334
|
+
p--;
|
|
335
|
+
const prevCh = p >= 0 ? source[p] : '';
|
|
336
|
+
if ('=(!,;:?[{&|^~+-><%'.includes(prevCh) || source.slice(Math.max(0, p - 5), p + 1).match(/\b(return|typeof|instanceof|in|of|void|delete|throw|new|case)\s*$/)) {
|
|
337
|
+
pos++;
|
|
338
|
+
while (pos < source.length) {
|
|
339
|
+
if (source[pos] === '\\')
|
|
340
|
+
pos++;
|
|
341
|
+
else if (source[pos] === '[') {
|
|
342
|
+
pos++;
|
|
343
|
+
while (pos < source.length && source[pos] !== ']') {
|
|
344
|
+
if (source[pos] === '\\')
|
|
345
|
+
pos++;
|
|
346
|
+
pos++;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else if (source[pos] === '/')
|
|
350
|
+
break;
|
|
351
|
+
pos++;
|
|
352
|
+
}
|
|
353
|
+
while (pos + 1 < source.length && /[gimsuy]/.test(source[pos + 1]))
|
|
354
|
+
pos++;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
330
357
|
pos++;
|
|
331
358
|
}
|
|
332
359
|
return -1; // not found
|
|
@@ -390,6 +417,11 @@ function findVarDeclarations(source, lineOffset = 0) {
|
|
|
390
417
|
const restOfLine = source.slice(vmatch.index + vmatch[0].length - 1, vmatch.index + vmatch[0].length + 200);
|
|
391
418
|
if (/^\s*require\s*\(/.test(restOfLine))
|
|
392
419
|
continue;
|
|
420
|
+
// Skip variable declarations inside for-loop headers (for (let x = ...; ...; ...))
|
|
421
|
+
// The semicolon inside for(...) is NOT a statement end
|
|
422
|
+
const beforeDecl = source.slice(Math.max(0, vmatch.index - 50), vmatch.index);
|
|
423
|
+
if (/\bfor\s*\(\s*$/.test(beforeDecl))
|
|
424
|
+
continue;
|
|
393
425
|
// Calculate line number (count newlines before this position)
|
|
394
426
|
// Subtract lineOffset to map compiled line numbers back to original source lines
|
|
395
427
|
let lineNo = 1;
|
|
@@ -421,8 +453,29 @@ function findVarDeclarations(source, lineOffset = 0) {
|
|
|
421
453
|
else if (ch === '\n' && depth === 0) {
|
|
422
454
|
// For semicolon-free code, the newline is the end
|
|
423
455
|
// But only if the next non-whitespace isn't a continuation (., +, etc.)
|
|
456
|
+
// AND the previous non-whitespace isn't an operator expecting more (=, +, -, etc.)
|
|
424
457
|
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
425
458
|
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
459
|
+
// Also check if a recent line ends with an operator that expects a value on the next line
|
|
460
|
+
// Walk backwards through empty lines to find the last non-empty line
|
|
461
|
+
let checkPos = pos;
|
|
462
|
+
let lastChar = '';
|
|
463
|
+
for (let back = 0; back < 5; back++) {
|
|
464
|
+
const prevNL = source.lastIndexOf('\n', checkPos - 1);
|
|
465
|
+
const prevLine = source.slice(prevNL + 1, checkPos).trimEnd();
|
|
466
|
+
if (prevLine.length > 0) {
|
|
467
|
+
lastChar = prevLine[prevLine.length - 1];
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
checkPos = prevNL;
|
|
471
|
+
if (prevNL <= 0)
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
if (lastChar && '=+-*/%&|^~<>?:,({['.includes(lastChar)) {
|
|
475
|
+
// Line ends with operator — this is a continuation, don't end the statement
|
|
476
|
+
pos++;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
426
479
|
foundEnd = pos;
|
|
427
480
|
break;
|
|
428
481
|
}
|
|
@@ -440,6 +493,38 @@ function findVarDeclarations(source, lineOffset = 0) {
|
|
|
440
493
|
pos++;
|
|
441
494
|
}
|
|
442
495
|
}
|
|
496
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] !== '/' && source[pos + 1] !== '*') {
|
|
497
|
+
// Possible regex literal — check if the preceding non-whitespace indicates regex context
|
|
498
|
+
// (after =, (, ,, ;, !, &, |, ^, ~, ?, :, [, {, return, typeof, etc.)
|
|
499
|
+
let p = pos - 1;
|
|
500
|
+
while (p >= 0 && (source[p] === ' ' || source[p] === '\t'))
|
|
501
|
+
p--;
|
|
502
|
+
const prevCh = p >= 0 ? source[p] : '';
|
|
503
|
+
if ('=(!,;:?[{&|^~+-><%'.includes(prevCh) || source.slice(Math.max(0, p - 5), p + 1).match(/\b(return|typeof|instanceof|in|of|void|delete|throw|new|case)\s*$/)) {
|
|
504
|
+
// This is a regex literal — skip to the closing /
|
|
505
|
+
pos++; // skip past opening /
|
|
506
|
+
while (pos < source.length) {
|
|
507
|
+
if (source[pos] === '\\') {
|
|
508
|
+
pos++;
|
|
509
|
+
} // skip escaped char
|
|
510
|
+
else if (source[pos] === '[') {
|
|
511
|
+
// Character class — skip to ]
|
|
512
|
+
pos++;
|
|
513
|
+
while (pos < source.length && source[pos] !== ']') {
|
|
514
|
+
if (source[pos] === '\\')
|
|
515
|
+
pos++;
|
|
516
|
+
pos++;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else if (source[pos] === '/')
|
|
520
|
+
break;
|
|
521
|
+
pos++;
|
|
522
|
+
}
|
|
523
|
+
// Skip regex flags
|
|
524
|
+
while (pos + 1 < source.length && /[gimsuy]/.test(source[pos + 1]))
|
|
525
|
+
pos++;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
443
528
|
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
444
529
|
// Skip line comment
|
|
445
530
|
while (pos < source.length && source[pos] !== '\n')
|
|
@@ -644,9 +729,14 @@ function transformCjsSource(source, filename, moduleName, env, sourceMap) {
|
|
|
644
729
|
while ((methodMatch = methodRegex.exec(classBody)) !== null) {
|
|
645
730
|
const isStatic = !!methodMatch[1];
|
|
646
731
|
const methodName = methodMatch[2];
|
|
647
|
-
// Skip constructor and
|
|
732
|
+
// Skip constructor, private methods, and JS keywords that look like method calls
|
|
648
733
|
if (methodName === 'constructor' || methodName.startsWith('_'))
|
|
649
734
|
continue;
|
|
735
|
+
if (['if', 'else', 'for', 'while', 'do', 'switch', 'case', 'return', 'throw',
|
|
736
|
+
'try', 'catch', 'finally', 'with', 'new', 'delete', 'typeof', 'void',
|
|
737
|
+
'yield', 'await', 'import', 'export', 'super', 'this', 'class',
|
|
738
|
+
'break', 'continue', 'debugger', 'in', 'of', 'instanceof'].includes(methodName))
|
|
739
|
+
continue;
|
|
650
740
|
// Extract param names
|
|
651
741
|
const mParamStr = methodMatch[3].trim();
|
|
652
742
|
const mParamNames = mParamStr
|
package/dist/trace-var.js
CHANGED
|
@@ -177,8 +177,11 @@ function flushVarBuffer() {
|
|
|
177
177
|
* More aggressive truncation than function samples since there are many more variables.
|
|
178
178
|
*/
|
|
179
179
|
function sanitizeVarSample(value, depth = 3) {
|
|
180
|
-
if (value === null
|
|
181
|
-
return
|
|
180
|
+
if (value === null)
|
|
181
|
+
return null;
|
|
182
|
+
// JSON.stringify drops undefined values, so use null to preserve the field
|
|
183
|
+
if (value === undefined)
|
|
184
|
+
return null;
|
|
182
185
|
const t = typeof value;
|
|
183
186
|
// Primitives are always safe to return at any depth
|
|
184
187
|
if (t === 'string') {
|
package/dist/vite-plugin.js
CHANGED
|
@@ -632,13 +632,25 @@ function findReassignments(source) {
|
|
|
632
632
|
const beforeOnLine = source.slice(lineStart, match.index).trim();
|
|
633
633
|
if (beforeOnLine.endsWith(':') || beforeOnLine.endsWith(','))
|
|
634
634
|
continue;
|
|
635
|
+
// Skip comma-separated multi-variable declaration continuations:
|
|
636
|
+
// var X = 'foo',
|
|
637
|
+
// Y = 'bar'; ← Y looks like a reassignment but is actually a declaration
|
|
638
|
+
// Detect by checking if the previous non-empty line ends with ','
|
|
639
|
+
if (beforeOnLine.length === 0) {
|
|
640
|
+
const prevLineEnd = source.lastIndexOf('\n', lineStart - 1);
|
|
641
|
+
if (prevLineEnd >= 0) {
|
|
642
|
+
const prevLine = source.slice(source.lastIndexOf('\n', prevLineEnd - 1) + 1, prevLineEnd).trimEnd();
|
|
643
|
+
if (prevLine.endsWith(','))
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
635
647
|
// Calculate line number
|
|
636
648
|
let lineNo = 1;
|
|
637
649
|
for (let i = 0; i < match.index; i++) {
|
|
638
650
|
if (source[i] === '\n')
|
|
639
651
|
lineNo++;
|
|
640
652
|
}
|
|
641
|
-
// Find end of statement
|
|
653
|
+
// Find end of statement
|
|
642
654
|
const startPos = match.index + match[0].length - 1;
|
|
643
655
|
let pos = startPos;
|
|
644
656
|
let depth = 0;
|
|
@@ -660,6 +672,24 @@ function findReassignments(source) {
|
|
|
660
672
|
else if (ch === '\n' && depth === 0) {
|
|
661
673
|
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
662
674
|
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
675
|
+
// Check if a recent non-empty line ends with an operator
|
|
676
|
+
let checkPos2 = pos;
|
|
677
|
+
let lastCh2 = '';
|
|
678
|
+
for (let back = 0; back < 5; back++) {
|
|
679
|
+
const prevNL2 = source.lastIndexOf('\n', checkPos2 - 1);
|
|
680
|
+
const prevLine2 = source.slice(prevNL2 + 1, checkPos2).trimEnd();
|
|
681
|
+
if (prevLine2.length > 0) {
|
|
682
|
+
lastCh2 = prevLine2[prevLine2.length - 1];
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
checkPos2 = prevNL2;
|
|
686
|
+
if (prevNL2 <= 0)
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
if (lastCh2 && '=+-*/%&|^~<>?:,({['.includes(lastCh2)) {
|
|
690
|
+
pos++;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
663
693
|
foundEnd = pos;
|
|
664
694
|
break;
|
|
665
695
|
}
|
|
@@ -676,6 +706,33 @@ function findReassignments(source) {
|
|
|
676
706
|
pos++;
|
|
677
707
|
}
|
|
678
708
|
}
|
|
709
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] !== '/' && source[pos + 1] !== '*') {
|
|
710
|
+
// Possible regex literal
|
|
711
|
+
let rp = pos - 1;
|
|
712
|
+
while (rp >= 0 && (source[rp] === ' ' || source[rp] === '\t'))
|
|
713
|
+
rp--;
|
|
714
|
+
const rpCh = rp >= 0 ? source[rp] : '';
|
|
715
|
+
if ('=(!,;:?[{&|^~+-><%'.includes(rpCh) || source.slice(Math.max(0, rp - 5), rp + 1).match(/\b(return|typeof|instanceof|in|of|void|delete|throw|new|case)\s*$/)) {
|
|
716
|
+
pos++;
|
|
717
|
+
while (pos < source.length) {
|
|
718
|
+
if (source[pos] === '\\')
|
|
719
|
+
pos++;
|
|
720
|
+
else if (source[pos] === '[') {
|
|
721
|
+
pos++;
|
|
722
|
+
while (pos < source.length && source[pos] !== ']') {
|
|
723
|
+
if (source[pos] === '\\')
|
|
724
|
+
pos++;
|
|
725
|
+
pos++;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else if (source[pos] === '/')
|
|
729
|
+
break;
|
|
730
|
+
pos++;
|
|
731
|
+
}
|
|
732
|
+
while (pos + 1 < source.length && /[gimsuy]/.test(source[pos + 1]))
|
|
733
|
+
pos++;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
679
736
|
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
680
737
|
while (pos < source.length && source[pos] !== '\n')
|
|
681
738
|
pos++;
|
package/dist-esm/vite-plugin.js
CHANGED
|
@@ -619,13 +619,25 @@ export function findReassignments(source) {
|
|
|
619
619
|
const beforeOnLine = source.slice(lineStart, match.index).trim();
|
|
620
620
|
if (beforeOnLine.endsWith(':') || beforeOnLine.endsWith(','))
|
|
621
621
|
continue;
|
|
622
|
+
// Skip comma-separated multi-variable declaration continuations:
|
|
623
|
+
// var X = 'foo',
|
|
624
|
+
// Y = 'bar'; ← Y looks like a reassignment but is actually a declaration
|
|
625
|
+
// Detect by checking if the previous non-empty line ends with ','
|
|
626
|
+
if (beforeOnLine.length === 0) {
|
|
627
|
+
const prevLineEnd = source.lastIndexOf('\n', lineStart - 1);
|
|
628
|
+
if (prevLineEnd >= 0) {
|
|
629
|
+
const prevLine = source.slice(source.lastIndexOf('\n', prevLineEnd - 1) + 1, prevLineEnd).trimEnd();
|
|
630
|
+
if (prevLine.endsWith(','))
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
622
634
|
// Calculate line number
|
|
623
635
|
let lineNo = 1;
|
|
624
636
|
for (let i = 0; i < match.index; i++) {
|
|
625
637
|
if (source[i] === '\n')
|
|
626
638
|
lineNo++;
|
|
627
639
|
}
|
|
628
|
-
// Find end of statement
|
|
640
|
+
// Find end of statement
|
|
629
641
|
const startPos = match.index + match[0].length - 1;
|
|
630
642
|
let pos = startPos;
|
|
631
643
|
let depth = 0;
|
|
@@ -647,6 +659,24 @@ export function findReassignments(source) {
|
|
|
647
659
|
else if (ch === '\n' && depth === 0) {
|
|
648
660
|
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
649
661
|
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
662
|
+
// Check if a recent non-empty line ends with an operator
|
|
663
|
+
let checkPos2 = pos;
|
|
664
|
+
let lastCh2 = '';
|
|
665
|
+
for (let back = 0; back < 5; back++) {
|
|
666
|
+
const prevNL2 = source.lastIndexOf('\n', checkPos2 - 1);
|
|
667
|
+
const prevLine2 = source.slice(prevNL2 + 1, checkPos2).trimEnd();
|
|
668
|
+
if (prevLine2.length > 0) {
|
|
669
|
+
lastCh2 = prevLine2[prevLine2.length - 1];
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
checkPos2 = prevNL2;
|
|
673
|
+
if (prevNL2 <= 0)
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
if (lastCh2 && '=+-*/%&|^~<>?:,({['.includes(lastCh2)) {
|
|
677
|
+
pos++;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
650
680
|
foundEnd = pos;
|
|
651
681
|
break;
|
|
652
682
|
}
|
|
@@ -663,6 +693,33 @@ export function findReassignments(source) {
|
|
|
663
693
|
pos++;
|
|
664
694
|
}
|
|
665
695
|
}
|
|
696
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] !== '/' && source[pos + 1] !== '*') {
|
|
697
|
+
// Possible regex literal
|
|
698
|
+
let rp = pos - 1;
|
|
699
|
+
while (rp >= 0 && (source[rp] === ' ' || source[rp] === '\t'))
|
|
700
|
+
rp--;
|
|
701
|
+
const rpCh = rp >= 0 ? source[rp] : '';
|
|
702
|
+
if ('=(!,;:?[{&|^~+-><%'.includes(rpCh) || source.slice(Math.max(0, rp - 5), rp + 1).match(/\b(return|typeof|instanceof|in|of|void|delete|throw|new|case)\s*$/)) {
|
|
703
|
+
pos++;
|
|
704
|
+
while (pos < source.length) {
|
|
705
|
+
if (source[pos] === '\\')
|
|
706
|
+
pos++;
|
|
707
|
+
else if (source[pos] === '[') {
|
|
708
|
+
pos++;
|
|
709
|
+
while (pos < source.length && source[pos] !== ']') {
|
|
710
|
+
if (source[pos] === '\\')
|
|
711
|
+
pos++;
|
|
712
|
+
pos++;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
else if (source[pos] === '/')
|
|
716
|
+
break;
|
|
717
|
+
pos++;
|
|
718
|
+
}
|
|
719
|
+
while (pos + 1 < source.length && /[gimsuy]/.test(source[pos + 1]))
|
|
720
|
+
pos++;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
666
723
|
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
667
724
|
while (pos < source.length && source[pos] !== '\n')
|
|
668
725
|
pos++;
|
package/package.json
CHANGED
package/src/observe-register.ts
CHANGED
|
@@ -334,6 +334,21 @@ function findClosingBrace(source: string, openBrace: number): number {
|
|
|
334
334
|
pos += 2;
|
|
335
335
|
while (pos < source.length - 1 && !(source[pos] === '*' && source[pos + 1] === '/')) pos++;
|
|
336
336
|
pos++; // skip past /
|
|
337
|
+
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] !== '/' && source[pos + 1] !== '*') {
|
|
338
|
+
// Possible regex literal — check preceding context
|
|
339
|
+
let p = pos - 1;
|
|
340
|
+
while (p >= 0 && (source[p] === ' ' || source[p] === '\t')) p--;
|
|
341
|
+
const prevCh = p >= 0 ? source[p] : '';
|
|
342
|
+
if ('=(!,;:?[{&|^~+-><%'.includes(prevCh) || source.slice(Math.max(0, p - 5), p + 1).match(/\b(return|typeof|instanceof|in|of|void|delete|throw|new|case)\s*$/)) {
|
|
343
|
+
pos++;
|
|
344
|
+
while (pos < source.length) {
|
|
345
|
+
if (source[pos] === '\\') pos++;
|
|
346
|
+
else if (source[pos] === '[') { pos++; while (pos < source.length && source[pos] !== ']') { if (source[pos] === '\\') pos++; pos++; } }
|
|
347
|
+
else if (source[pos] === '/') break;
|
|
348
|
+
pos++;
|
|
349
|
+
}
|
|
350
|
+
while (pos + 1 < source.length && /[gimsuy]/.test(source[pos + 1])) pos++;
|
|
351
|
+
}
|
|
337
352
|
}
|
|
338
353
|
pos++;
|
|
339
354
|
}
|
|
@@ -391,6 +406,11 @@ function findVarDeclarations(source: string, lineOffset: number = 0): Array<{ li
|
|
|
391
406
|
const restOfLine = source.slice(vmatch.index + vmatch[0].length - 1, vmatch.index + vmatch[0].length + 200);
|
|
392
407
|
if (/^\s*require\s*\(/.test(restOfLine)) continue;
|
|
393
408
|
|
|
409
|
+
// Skip variable declarations inside for-loop headers (for (let x = ...; ...; ...))
|
|
410
|
+
// The semicolon inside for(...) is NOT a statement end
|
|
411
|
+
const beforeDecl = source.slice(Math.max(0, vmatch.index - 50), vmatch.index);
|
|
412
|
+
if (/\bfor\s*\(\s*$/.test(beforeDecl)) continue;
|
|
413
|
+
|
|
394
414
|
// Calculate line number (count newlines before this position)
|
|
395
415
|
// Subtract lineOffset to map compiled line numbers back to original source lines
|
|
396
416
|
let lineNo = 1;
|
|
@@ -419,8 +439,28 @@ function findVarDeclarations(source: string, lineOffset: number = 0): Array<{ li
|
|
|
419
439
|
} else if (ch === '\n' && depth === 0) {
|
|
420
440
|
// For semicolon-free code, the newline is the end
|
|
421
441
|
// But only if the next non-whitespace isn't a continuation (., +, etc.)
|
|
442
|
+
// AND the previous non-whitespace isn't an operator expecting more (=, +, -, etc.)
|
|
422
443
|
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
423
444
|
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
445
|
+
// Also check if a recent line ends with an operator that expects a value on the next line
|
|
446
|
+
// Walk backwards through empty lines to find the last non-empty line
|
|
447
|
+
let checkPos = pos;
|
|
448
|
+
let lastChar = '';
|
|
449
|
+
for (let back = 0; back < 5; back++) {
|
|
450
|
+
const prevNL = source.lastIndexOf('\n', checkPos - 1);
|
|
451
|
+
const prevLine = source.slice(prevNL + 1, checkPos).trimEnd();
|
|
452
|
+
if (prevLine.length > 0) {
|
|
453
|
+
lastChar = prevLine[prevLine.length - 1];
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
checkPos = prevNL;
|
|
457
|
+
if (prevNL <= 0) break;
|
|
458
|
+
}
|
|
459
|
+
if (lastChar && '=+-*/%&|^~<>?:,({['.includes(lastChar)) {
|
|
460
|
+
// Line ends with operator — this is a continuation, don't end the statement
|
|
461
|
+
pos++;
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
424
464
|
foundEnd = pos;
|
|
425
465
|
break;
|
|
426
466
|
}
|
|
@@ -433,6 +473,30 @@ function findVarDeclarations(source: string, lineOffset: number = 0): Array<{ li
|
|
|
433
473
|
else if (source[pos] === quote) break;
|
|
434
474
|
pos++;
|
|
435
475
|
}
|
|
476
|
+
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] !== '/' && source[pos + 1] !== '*') {
|
|
477
|
+
// Possible regex literal — check if the preceding non-whitespace indicates regex context
|
|
478
|
+
// (after =, (, ,, ;, !, &, |, ^, ~, ?, :, [, {, return, typeof, etc.)
|
|
479
|
+
let p = pos - 1;
|
|
480
|
+
while (p >= 0 && (source[p] === ' ' || source[p] === '\t')) p--;
|
|
481
|
+
const prevCh = p >= 0 ? source[p] : '';
|
|
482
|
+
if ('=(!,;:?[{&|^~+-><%'.includes(prevCh) || source.slice(Math.max(0, p - 5), p + 1).match(/\b(return|typeof|instanceof|in|of|void|delete|throw|new|case)\s*$/)) {
|
|
483
|
+
// This is a regex literal — skip to the closing /
|
|
484
|
+
pos++; // skip past opening /
|
|
485
|
+
while (pos < source.length) {
|
|
486
|
+
if (source[pos] === '\\') { pos++; } // skip escaped char
|
|
487
|
+
else if (source[pos] === '[') {
|
|
488
|
+
// Character class — skip to ]
|
|
489
|
+
pos++;
|
|
490
|
+
while (pos < source.length && source[pos] !== ']') {
|
|
491
|
+
if (source[pos] === '\\') pos++;
|
|
492
|
+
pos++;
|
|
493
|
+
}
|
|
494
|
+
} else if (source[pos] === '/') break;
|
|
495
|
+
pos++;
|
|
496
|
+
}
|
|
497
|
+
// Skip regex flags
|
|
498
|
+
while (pos + 1 < source.length && /[gimsuy]/.test(source[pos + 1])) pos++;
|
|
499
|
+
}
|
|
436
500
|
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
437
501
|
// Skip line comment
|
|
438
502
|
while (pos < source.length && source[pos] !== '\n') pos++;
|
|
@@ -627,8 +691,12 @@ function transformCjsSource(source: string, filename: string, moduleName: string
|
|
|
627
691
|
while ((methodMatch = methodRegex.exec(classBody)) !== null) {
|
|
628
692
|
const isStatic = !!methodMatch[1];
|
|
629
693
|
const methodName = methodMatch[2];
|
|
630
|
-
// Skip constructor and
|
|
694
|
+
// Skip constructor, private methods, and JS keywords that look like method calls
|
|
631
695
|
if (methodName === 'constructor' || methodName.startsWith('_')) continue;
|
|
696
|
+
if (['if', 'else', 'for', 'while', 'do', 'switch', 'case', 'return', 'throw',
|
|
697
|
+
'try', 'catch', 'finally', 'with', 'new', 'delete', 'typeof', 'void',
|
|
698
|
+
'yield', 'await', 'import', 'export', 'super', 'this', 'class',
|
|
699
|
+
'break', 'continue', 'debugger', 'in', 'of', 'instanceof'].includes(methodName)) continue;
|
|
632
700
|
// Extract param names
|
|
633
701
|
const mParamStr = methodMatch[3].trim();
|
|
634
702
|
const mParamNames = mParamStr
|
package/src/trace-var.ts
CHANGED
|
@@ -167,7 +167,9 @@ function flushVarBuffer(): void {
|
|
|
167
167
|
* More aggressive truncation than function samples since there are many more variables.
|
|
168
168
|
*/
|
|
169
169
|
function sanitizeVarSample(value: unknown, depth: number = 3): unknown {
|
|
170
|
-
if (value === null
|
|
170
|
+
if (value === null) return null;
|
|
171
|
+
// JSON.stringify drops undefined values, so use null to preserve the field
|
|
172
|
+
if (value === undefined) return null;
|
|
171
173
|
|
|
172
174
|
const t = typeof value;
|
|
173
175
|
// Primitives are always safe to return at any depth
|
package/src/vite-plugin.ts
CHANGED
|
@@ -578,13 +578,25 @@ export function findReassignments(source: string): Array<{ lineEnd: number; varN
|
|
|
578
578
|
const beforeOnLine = source.slice(lineStart, match.index).trim();
|
|
579
579
|
if (beforeOnLine.endsWith(':') || beforeOnLine.endsWith(',')) continue;
|
|
580
580
|
|
|
581
|
+
// Skip comma-separated multi-variable declaration continuations:
|
|
582
|
+
// var X = 'foo',
|
|
583
|
+
// Y = 'bar'; ← Y looks like a reassignment but is actually a declaration
|
|
584
|
+
// Detect by checking if the previous non-empty line ends with ','
|
|
585
|
+
if (beforeOnLine.length === 0) {
|
|
586
|
+
const prevLineEnd = source.lastIndexOf('\n', lineStart - 1);
|
|
587
|
+
if (prevLineEnd >= 0) {
|
|
588
|
+
const prevLine = source.slice(source.lastIndexOf('\n', prevLineEnd - 1) + 1, prevLineEnd).trimEnd();
|
|
589
|
+
if (prevLine.endsWith(',')) continue;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
581
593
|
// Calculate line number
|
|
582
594
|
let lineNo = 1;
|
|
583
595
|
for (let i = 0; i < match.index; i++) {
|
|
584
596
|
if (source[i] === '\n') lineNo++;
|
|
585
597
|
}
|
|
586
598
|
|
|
587
|
-
// Find end of statement
|
|
599
|
+
// Find end of statement
|
|
588
600
|
const startPos = match.index + match[0].length - 1;
|
|
589
601
|
let pos = startPos;
|
|
590
602
|
let depth = 0;
|
|
@@ -603,6 +615,19 @@ export function findReassignments(source: string): Array<{ lineEnd: number; varN
|
|
|
603
615
|
} else if (ch === '\n' && depth === 0) {
|
|
604
616
|
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
605
617
|
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
618
|
+
// Check if a recent non-empty line ends with an operator
|
|
619
|
+
let checkPos2 = pos;
|
|
620
|
+
let lastCh2 = '';
|
|
621
|
+
for (let back = 0; back < 5; back++) {
|
|
622
|
+
const prevNL2 = source.lastIndexOf('\n', checkPos2 - 1);
|
|
623
|
+
const prevLine2 = source.slice(prevNL2 + 1, checkPos2).trimEnd();
|
|
624
|
+
if (prevLine2.length > 0) { lastCh2 = prevLine2[prevLine2.length - 1]; break; }
|
|
625
|
+
checkPos2 = prevNL2;
|
|
626
|
+
if (prevNL2 <= 0) break;
|
|
627
|
+
}
|
|
628
|
+
if (lastCh2 && '=+-*/%&|^~<>?:,({['.includes(lastCh2)) {
|
|
629
|
+
pos++; continue;
|
|
630
|
+
}
|
|
606
631
|
foundEnd = pos;
|
|
607
632
|
break;
|
|
608
633
|
}
|
|
@@ -614,6 +639,21 @@ export function findReassignments(source: string): Array<{ lineEnd: number; varN
|
|
|
614
639
|
else if (source[pos] === quote) break;
|
|
615
640
|
pos++;
|
|
616
641
|
}
|
|
642
|
+
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] !== '/' && source[pos + 1] !== '*') {
|
|
643
|
+
// Possible regex literal
|
|
644
|
+
let rp = pos - 1;
|
|
645
|
+
while (rp >= 0 && (source[rp] === ' ' || source[rp] === '\t')) rp--;
|
|
646
|
+
const rpCh = rp >= 0 ? source[rp] : '';
|
|
647
|
+
if ('=(!,;:?[{&|^~+-><%'.includes(rpCh) || source.slice(Math.max(0, rp - 5), rp + 1).match(/\b(return|typeof|instanceof|in|of|void|delete|throw|new|case)\s*$/)) {
|
|
648
|
+
pos++;
|
|
649
|
+
while (pos < source.length) {
|
|
650
|
+
if (source[pos] === '\\') pos++;
|
|
651
|
+
else if (source[pos] === '[') { pos++; while (pos < source.length && source[pos] !== ']') { if (source[pos] === '\\') pos++; pos++; } }
|
|
652
|
+
else if (source[pos] === '/') break;
|
|
653
|
+
pos++;
|
|
654
|
+
}
|
|
655
|
+
while (pos + 1 < source.length && /[gimsuy]/.test(source[pos + 1])) pos++;
|
|
656
|
+
}
|
|
617
657
|
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
618
658
|
while (pos < source.length && source[pos] !== '\n') pos++;
|
|
619
659
|
continue;
|