trickle-observe 0.2.24 → 0.2.26

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.
@@ -166,11 +166,11 @@ function findClosingBrace(source, openBrace) {
166
166
  * Handles: const x = ...; let x = ...; var x = ...;
167
167
  * Skips: destructuring, for-loop vars, require() calls, imports.
168
168
  */
169
- function findVarDeclarations(source) {
169
+ function findVarDeclarations(source, lineOffset = 0) {
170
170
  const varInsertions = [];
171
- // Match: const/let/var <identifier> = <something>
172
- // We look for lines containing a simple variable declaration with an assignment
173
- const varRegex = /^([ \t]*)(const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=[^=]/gm;
171
+ // Match: const/let/var <identifier>[: TypeAnnotation] = <something>
172
+ // Handles both JS (no annotation) and TS (with annotation like `: string` or `: MyType`)
173
+ const varRegex = /^([ \t]*)(?:export\s+)?(const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*:[^=]+?)?\s*=[^=]/gm;
174
174
  let vmatch;
175
175
  while ((vmatch = varRegex.exec(source)) !== null) {
176
176
  const varName = vmatch[3];
@@ -186,11 +186,13 @@ function findVarDeclarations(source) {
186
186
  if (/^\s*require\s*\(/.test(restOfLine))
187
187
  continue;
188
188
  // Calculate line number (count newlines before this position)
189
+ // Subtract lineOffset to map compiled line numbers back to original source lines
189
190
  let lineNo = 1;
190
191
  for (let i = 0; i < vmatch.index; i++) {
191
192
  if (source[i] === '\n')
192
193
  lineNo++;
193
194
  }
195
+ lineNo = Math.max(1, lineNo - lineOffset);
194
196
  // Find the end of this statement — look for the semicolon at depth 0
195
197
  // or the end of the line for semicolon-free code
196
198
  const startPos = vmatch.index + vmatch[0].length - 1; // position of the '='
@@ -257,7 +259,7 @@ function findVarDeclarations(source) {
257
259
  /**
258
260
  * Find destructured variable declarations: const { a, b } = ... and const [a, b] = ...
259
261
  */
260
- function findDestructuredDeclarations(source) {
262
+ function findDestructuredDeclarations(source, lineOffset = 0) {
261
263
  const results = [];
262
264
  const destructRegex = /^[ \t]*(?:export\s+)?(?:const|let|var)\s+(\{[^}]*\}|\[[^\]]*\])\s*(?::\s*[^=]+?)?\s*=[^=]/gm;
263
265
  let match;
@@ -274,6 +276,7 @@ function findDestructuredDeclarations(source) {
274
276
  if (source[i] === '\n')
275
277
  lineNo++;
276
278
  }
279
+ lineNo = Math.max(1, lineNo - lineOffset);
277
280
  const startPos = match.index + match[0].length - 1;
278
281
  let pos = startPos;
279
282
  let depth = 0;
@@ -417,8 +420,82 @@ function transformCjsSource(source, filename, moduleName, env) {
417
420
  }
418
421
  // Also find variable declarations for tracing
419
422
  const varTraceEnabled = process.env.TRICKLE_TRACE_VARS !== '0';
420
- const varInsertions = varTraceEnabled ? findVarDeclarations(source) : [];
421
- const destructInsertions = varTraceEnabled ? findDestructuredDeclarations(source) : [];
423
+ // For TypeScript files (compiled by ts-node/tsc), type declarations (interfaces, type aliases)
424
+ // are stripped from the compiled JS, shifting line numbers. The only accurate way to get correct
425
+ // line numbers is to read the original .ts source file and parse it directly.
426
+ // For plain JS files, we parse the source directly.
427
+ let varInsertions = [];
428
+ let destructInsertions = [];
429
+ if (varTraceEnabled) {
430
+ const isTsFile = /\.[mc]?tsx?$/.test(filename);
431
+ if (isTsFile) {
432
+ try {
433
+ // Read original TypeScript source to get correct line numbers
434
+ const fs = require('fs');
435
+ const originalSource = fs.readFileSync(filename, 'utf8');
436
+ const tsVarInsertions = findVarDeclarations(originalSource);
437
+ const tsDestructInsertions = findDestructuredDeclarations(originalSource);
438
+ // Map TypeScript variable names → correct line numbers (by occurrence order)
439
+ // Use a counter map to handle duplicate variable names in different scopes
440
+ const tsLineByVarAndOccurrence = new Map();
441
+ for (const { varName, lineNo } of tsVarInsertions) {
442
+ if (!tsLineByVarAndOccurrence.has(varName))
443
+ tsLineByVarAndOccurrence.set(varName, []);
444
+ tsLineByVarAndOccurrence.get(varName).push(lineNo);
445
+ }
446
+ // Find positions in compiled JS (for inserting trace calls),
447
+ // but use line numbers from original TypeScript source
448
+ const compiledInsertions = findVarDeclarations(source);
449
+ const varOccurrenceCounter = new Map();
450
+ for (const ins of compiledInsertions) {
451
+ const occCount = (varOccurrenceCounter.get(ins.varName) || 0);
452
+ varOccurrenceCounter.set(ins.varName, occCount + 1);
453
+ const tsLines = tsLineByVarAndOccurrence.get(ins.varName);
454
+ const correctLineNo = tsLines ? (tsLines[occCount] ?? tsLines[tsLines.length - 1]) : undefined;
455
+ varInsertions.push({ ...ins, lineNo: correctLineNo ?? ins.lineNo });
456
+ }
457
+ const compiledDestructInsertions = findDestructuredDeclarations(source);
458
+ destructInsertions = compiledDestructInsertions; // Keep compiled line numbers as fallback
459
+ // Try to fix destructured line numbers too
460
+ const tsDestructByKey = new Map();
461
+ for (const { varNames, lineNo } of tsDestructInsertions) {
462
+ const key = varNames.join(',');
463
+ if (!tsDestructByKey.has(key))
464
+ tsDestructByKey.set(key, []);
465
+ tsDestructByKey.get(key).push(lineNo);
466
+ }
467
+ const destructOccCounter = new Map();
468
+ destructInsertions = compiledDestructInsertions.map(ins => {
469
+ const key = ins.varNames.join(',');
470
+ const occ = destructOccCounter.get(key) || 0;
471
+ destructOccCounter.set(key, occ + 1);
472
+ const tsLines = tsDestructByKey.get(key);
473
+ const correctLineNo = tsLines ? (tsLines[occ] ?? tsLines[tsLines.length - 1]) : undefined;
474
+ return { ...ins, lineNo: correctLineNo ?? ins.lineNo };
475
+ });
476
+ }
477
+ catch {
478
+ // Fallback: use compiled line numbers with prologue offset
479
+ let lineOffset = 0;
480
+ const lines = source.split('\n');
481
+ for (const line of lines) {
482
+ const trimmed = line.trim();
483
+ if (trimmed === '"use strict";' || trimmed === "'use strict';" || trimmed === '') {
484
+ lineOffset++;
485
+ }
486
+ else {
487
+ break;
488
+ }
489
+ }
490
+ varInsertions = findVarDeclarations(source, lineOffset);
491
+ destructInsertions = findDestructuredDeclarations(source, lineOffset);
492
+ }
493
+ }
494
+ else {
495
+ varInsertions = findVarDeclarations(source);
496
+ destructInsertions = findDestructuredDeclarations(source);
497
+ }
498
+ }
422
499
  if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0)
423
500
  return source;
424
501
  // Resolve the path to the wrap helper (compiled JS)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -153,12 +153,12 @@ function findClosingBrace(source: string, openBrace: number): number {
153
153
  * Handles: const x = ...; let x = ...; var x = ...;
154
154
  * Skips: destructuring, for-loop vars, require() calls, imports.
155
155
  */
156
- function findVarDeclarations(source: string): Array<{ lineEnd: number; varName: string; lineNo: number }> {
156
+ function findVarDeclarations(source: string, lineOffset: number = 0): Array<{ lineEnd: number; varName: string; lineNo: number }> {
157
157
  const varInsertions: Array<{ lineEnd: number; varName: string; lineNo: number }> = [];
158
158
 
159
- // Match: const/let/var <identifier> = <something>
160
- // We look for lines containing a simple variable declaration with an assignment
161
- const varRegex = /^([ \t]*)(const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=[^=]/gm;
159
+ // Match: const/let/var <identifier>[: TypeAnnotation] = <something>
160
+ // Handles both JS (no annotation) and TS (with annotation like `: string` or `: MyType`)
161
+ const varRegex = /^([ \t]*)(?:export\s+)?(const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*:[^=]+?)?\s*=[^=]/gm;
162
162
  let vmatch;
163
163
 
164
164
  while ((vmatch = varRegex.exec(source)) !== null) {
@@ -174,10 +174,12 @@ function findVarDeclarations(source: string): Array<{ lineEnd: number; varName:
174
174
  if (/^\s*require\s*\(/.test(restOfLine)) continue;
175
175
 
176
176
  // Calculate line number (count newlines before this position)
177
+ // Subtract lineOffset to map compiled line numbers back to original source lines
177
178
  let lineNo = 1;
178
179
  for (let i = 0; i < vmatch.index; i++) {
179
180
  if (source[i] === '\n') lineNo++;
180
181
  }
182
+ lineNo = Math.max(1, lineNo - lineOffset);
181
183
 
182
184
  // Find the end of this statement — look for the semicolon at depth 0
183
185
  // or the end of the line for semicolon-free code
@@ -237,7 +239,7 @@ function findVarDeclarations(source: string): Array<{ lineEnd: number; varName:
237
239
  /**
238
240
  * Find destructured variable declarations: const { a, b } = ... and const [a, b] = ...
239
241
  */
240
- function findDestructuredDeclarations(source: string): Array<{ lineEnd: number; varNames: string[]; lineNo: number }> {
242
+ function findDestructuredDeclarations(source: string, lineOffset: number = 0): Array<{ lineEnd: number; varNames: string[]; lineNo: number }> {
241
243
  const results: Array<{ lineEnd: number; varNames: string[]; lineNo: number }> = [];
242
244
 
243
245
  const destructRegex = /^[ \t]*(?:export\s+)?(?:const|let|var)\s+(\{[^}]*\}|\[[^\]]*\])\s*(?::\s*[^=]+?)?\s*=[^=]/gm;
@@ -255,6 +257,7 @@ function findDestructuredDeclarations(source: string): Array<{ lineEnd: number;
255
257
  for (let i = 0; i < match.index; i++) {
256
258
  if (source[i] === '\n') lineNo++;
257
259
  }
260
+ lineNo = Math.max(1, lineNo - lineOffset);
258
261
 
259
262
  const startPos = match.index + match[0].length - 1;
260
263
  let pos = startPos;
@@ -388,8 +391,82 @@ function transformCjsSource(source: string, filename: string, moduleName: string
388
391
 
389
392
  // Also find variable declarations for tracing
390
393
  const varTraceEnabled = process.env.TRICKLE_TRACE_VARS !== '0';
391
- const varInsertions = varTraceEnabled ? findVarDeclarations(source) : [];
392
- const destructInsertions = varTraceEnabled ? findDestructuredDeclarations(source) : [];
394
+
395
+ // For TypeScript files (compiled by ts-node/tsc), type declarations (interfaces, type aliases)
396
+ // are stripped from the compiled JS, shifting line numbers. The only accurate way to get correct
397
+ // line numbers is to read the original .ts source file and parse it directly.
398
+ // For plain JS files, we parse the source directly.
399
+ let varInsertions: Array<{ lineEnd: number; varName: string; lineNo: number }> = [];
400
+ let destructInsertions: Array<{ lineEnd: number; varNames: string[]; lineNo: number }> = [];
401
+
402
+ if (varTraceEnabled) {
403
+ const isTsFile = /\.[mc]?tsx?$/.test(filename);
404
+ if (isTsFile) {
405
+ try {
406
+ // Read original TypeScript source to get correct line numbers
407
+ const fs = require('fs');
408
+ const originalSource: string = fs.readFileSync(filename, 'utf8');
409
+ const tsVarInsertions = findVarDeclarations(originalSource);
410
+ const tsDestructInsertions = findDestructuredDeclarations(originalSource);
411
+
412
+ // Map TypeScript variable names → correct line numbers (by occurrence order)
413
+ // Use a counter map to handle duplicate variable names in different scopes
414
+ const tsLineByVarAndOccurrence = new Map<string, number[]>();
415
+ for (const { varName, lineNo } of tsVarInsertions) {
416
+ if (!tsLineByVarAndOccurrence.has(varName)) tsLineByVarAndOccurrence.set(varName, []);
417
+ tsLineByVarAndOccurrence.get(varName)!.push(lineNo);
418
+ }
419
+
420
+ // Find positions in compiled JS (for inserting trace calls),
421
+ // but use line numbers from original TypeScript source
422
+ const compiledInsertions = findVarDeclarations(source);
423
+ const varOccurrenceCounter = new Map<string, number>();
424
+ for (const ins of compiledInsertions) {
425
+ const occCount = (varOccurrenceCounter.get(ins.varName) || 0);
426
+ varOccurrenceCounter.set(ins.varName, occCount + 1);
427
+ const tsLines = tsLineByVarAndOccurrence.get(ins.varName);
428
+ const correctLineNo = tsLines ? (tsLines[occCount] ?? tsLines[tsLines.length - 1]) : undefined;
429
+ varInsertions.push({ ...ins, lineNo: correctLineNo ?? ins.lineNo });
430
+ }
431
+
432
+ const compiledDestructInsertions = findDestructuredDeclarations(source);
433
+ destructInsertions = compiledDestructInsertions; // Keep compiled line numbers as fallback
434
+ // Try to fix destructured line numbers too
435
+ const tsDestructByKey = new Map<string, number[]>();
436
+ for (const { varNames, lineNo } of tsDestructInsertions) {
437
+ const key = varNames.join(',');
438
+ if (!tsDestructByKey.has(key)) tsDestructByKey.set(key, []);
439
+ tsDestructByKey.get(key)!.push(lineNo);
440
+ }
441
+ const destructOccCounter = new Map<string, number>();
442
+ destructInsertions = compiledDestructInsertions.map(ins => {
443
+ const key = ins.varNames.join(',');
444
+ const occ = destructOccCounter.get(key) || 0;
445
+ destructOccCounter.set(key, occ + 1);
446
+ const tsLines = tsDestructByKey.get(key);
447
+ const correctLineNo = tsLines ? (tsLines[occ] ?? tsLines[tsLines.length - 1]) : undefined;
448
+ return { ...ins, lineNo: correctLineNo ?? ins.lineNo };
449
+ });
450
+ } catch {
451
+ // Fallback: use compiled line numbers with prologue offset
452
+ let lineOffset = 0;
453
+ const lines = source.split('\n');
454
+ for (const line of lines) {
455
+ const trimmed = line.trim();
456
+ if (trimmed === '"use strict";' || trimmed === "'use strict';" || trimmed === '') {
457
+ lineOffset++;
458
+ } else {
459
+ break;
460
+ }
461
+ }
462
+ varInsertions = findVarDeclarations(source, lineOffset);
463
+ destructInsertions = findDestructuredDeclarations(source, lineOffset);
464
+ }
465
+ } else {
466
+ varInsertions = findVarDeclarations(source);
467
+ destructInsertions = findDestructuredDeclarations(source);
468
+ }
469
+ }
393
470
 
394
471
  if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0) return source;
395
472