trickle-observe 0.2.86 → 0.2.87

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.
@@ -390,6 +390,11 @@ function findVarDeclarations(source, lineOffset = 0) {
390
390
  const restOfLine = source.slice(vmatch.index + vmatch[0].length - 1, vmatch.index + vmatch[0].length + 200);
391
391
  if (/^\s*require\s*\(/.test(restOfLine))
392
392
  continue;
393
+ // Skip variable declarations inside for-loop headers (for (let x = ...; ...; ...))
394
+ // The semicolon inside for(...) is NOT a statement end
395
+ const beforeDecl = source.slice(Math.max(0, vmatch.index - 50), vmatch.index);
396
+ if (/\bfor\s*\(\s*$/.test(beforeDecl))
397
+ continue;
393
398
  // Calculate line number (count newlines before this position)
394
399
  // Subtract lineOffset to map compiled line numbers back to original source lines
395
400
  let lineNo = 1;
@@ -421,8 +426,17 @@ function findVarDeclarations(source, lineOffset = 0) {
421
426
  else if (ch === '\n' && depth === 0) {
422
427
  // For semicolon-free code, the newline is the end
423
428
  // But only if the next non-whitespace isn't a continuation (., +, etc.)
429
+ // AND the previous non-whitespace isn't an operator expecting more (=, +, -, etc.)
424
430
  const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
425
431
  if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
432
+ // Also check if the line ends with an operator that expects a value on the next line
433
+ const prevOnLine = source.slice(source.lastIndexOf('\n', pos - 1) + 1, pos).trimEnd();
434
+ const lastChar = prevOnLine[prevOnLine.length - 1];
435
+ if (lastChar && '=+-*/%&|^~<>?:,({['.includes(lastChar)) {
436
+ // Line ends with operator — this is a continuation, don't end the statement
437
+ pos++;
438
+ continue;
439
+ }
426
440
  foundEnd = pos;
427
441
  break;
428
442
  }
@@ -644,9 +658,14 @@ function transformCjsSource(source, filename, moduleName, env, sourceMap) {
644
658
  while ((methodMatch = methodRegex.exec(classBody)) !== null) {
645
659
  const isStatic = !!methodMatch[1];
646
660
  const methodName = methodMatch[2];
647
- // Skip constructor and private methods
661
+ // Skip constructor, private methods, and JS keywords that look like method calls
648
662
  if (methodName === 'constructor' || methodName.startsWith('_'))
649
663
  continue;
664
+ if (['if', 'else', 'for', 'while', 'do', 'switch', 'case', 'return', 'throw',
665
+ 'try', 'catch', 'finally', 'with', 'new', 'delete', 'typeof', 'void',
666
+ 'yield', 'await', 'import', 'export', 'super', 'this', 'class',
667
+ 'break', 'continue', 'debugger', 'in', 'of', 'instanceof'].includes(methodName))
668
+ continue;
650
669
  // Extract param names
651
670
  const mParamStr = methodMatch[3].trim();
652
671
  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 || value === undefined)
181
- return value;
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.86",
3
+ "version": "0.2.87",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -391,6 +391,11 @@ function findVarDeclarations(source: string, lineOffset: number = 0): Array<{ li
391
391
  const restOfLine = source.slice(vmatch.index + vmatch[0].length - 1, vmatch.index + vmatch[0].length + 200);
392
392
  if (/^\s*require\s*\(/.test(restOfLine)) continue;
393
393
 
394
+ // Skip variable declarations inside for-loop headers (for (let x = ...; ...; ...))
395
+ // The semicolon inside for(...) is NOT a statement end
396
+ const beforeDecl = source.slice(Math.max(0, vmatch.index - 50), vmatch.index);
397
+ if (/\bfor\s*\(\s*$/.test(beforeDecl)) continue;
398
+
394
399
  // Calculate line number (count newlines before this position)
395
400
  // Subtract lineOffset to map compiled line numbers back to original source lines
396
401
  let lineNo = 1;
@@ -419,8 +424,17 @@ function findVarDeclarations(source: string, lineOffset: number = 0): Array<{ li
419
424
  } else if (ch === '\n' && depth === 0) {
420
425
  // For semicolon-free code, the newline is the end
421
426
  // But only if the next non-whitespace isn't a continuation (., +, etc.)
427
+ // AND the previous non-whitespace isn't an operator expecting more (=, +, -, etc.)
422
428
  const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
423
429
  if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
430
+ // Also check if the line ends with an operator that expects a value on the next line
431
+ const prevOnLine = source.slice(source.lastIndexOf('\n', pos - 1) + 1, pos).trimEnd();
432
+ const lastChar = prevOnLine[prevOnLine.length - 1];
433
+ if (lastChar && '=+-*/%&|^~<>?:,({['.includes(lastChar)) {
434
+ // Line ends with operator — this is a continuation, don't end the statement
435
+ pos++;
436
+ continue;
437
+ }
424
438
  foundEnd = pos;
425
439
  break;
426
440
  }
@@ -627,8 +641,12 @@ function transformCjsSource(source: string, filename: string, moduleName: string
627
641
  while ((methodMatch = methodRegex.exec(classBody)) !== null) {
628
642
  const isStatic = !!methodMatch[1];
629
643
  const methodName = methodMatch[2];
630
- // Skip constructor and private methods
644
+ // Skip constructor, private methods, and JS keywords that look like method calls
631
645
  if (methodName === 'constructor' || methodName.startsWith('_')) continue;
646
+ if (['if', 'else', 'for', 'while', 'do', 'switch', 'case', 'return', 'throw',
647
+ 'try', 'catch', 'finally', 'with', 'new', 'delete', 'typeof', 'void',
648
+ 'yield', 'await', 'import', 'export', 'super', 'this', 'class',
649
+ 'break', 'continue', 'debugger', 'in', 'of', 'instanceof'].includes(methodName)) continue;
632
650
  // Extract param names
633
651
  const mParamStr = methodMatch[3].trim();
634
652
  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 || value === undefined) return value;
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