taist 0.1.10 → 0.1.12

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.
@@ -77,12 +77,14 @@ export function shouldInstrument(modulePath, config) {
77
77
  * Simple glob matching (supports * and **)
78
78
  */
79
79
  export function matchGlob(str, pattern) {
80
- // Convert glob pattern to regex
80
+ // Convert glob pattern to regex using placeholders to prevent double-replacement
81
81
  const regexPattern = pattern
82
82
  .replace(/[.+^${}()|[\]\\]/g, "\\$&") // Escape special chars
83
- .replace(/\*\*\//g, "(?:.*\\/)?") // **/ matches zero or more directories
84
- .replace(/\*\*/g, ".*") // ** matches anything
85
- .replace(/\*/g, "[^/]*"); // * matches any chars except /
83
+ .replace(/\*\*\//g, "\0ANYDIR\0") // Placeholder for **/
84
+ .replace(/\*\*/g, "\0ANY\0") // Placeholder for **
85
+ .replace(/\*/g, "[^/]*") // * matches any chars except /
86
+ .replace(/\0ANYDIR\0/g, "(?:.*\\/)?") // Restore **/ - matches zero or more directories
87
+ .replace(/\0ANY\0/g, ".*"); // Restore ** - matches anything
86
88
 
87
89
  const regex = new RegExp(`^${regexPattern}$`);
88
90
  return regex.test(str);
@@ -70,15 +70,6 @@ export class ToonFormatter {
70
70
  });
71
71
  }
72
72
 
73
- // Trace
74
- if (results.trace && results.trace.length > 0) {
75
- lines.push('');
76
- lines.push('TRACE:');
77
- results.trace.forEach(entry => {
78
- lines.push(this.formatTraceEntry(entry));
79
- });
80
- }
81
-
82
73
  // Coverage
83
74
  if (results.coverage) {
84
75
  lines.push('');
@@ -154,46 +145,6 @@ export class ToonFormatter {
154
145
  return lines;
155
146
  }
156
147
 
157
- /**
158
- * Format trace entry with depth-based indentation for execution tree
159
- */
160
- formatTraceEntry(entry) {
161
- const parts = [];
162
-
163
- // Function name
164
- parts.push(`fn:${this.abbreviateFunctionName(entry.name)}`);
165
-
166
- // Duration
167
- if (entry.duration !== undefined) {
168
- parts.push(`ms:${Math.round(entry.duration)}`);
169
- }
170
-
171
- // Arguments (if present)
172
- if (entry.args && entry.args.length > 0) {
173
- const args = entry.args
174
- .slice(0, this.options.maxArrayItems)
175
- .map(arg => this.formatValue(arg))
176
- .join(',');
177
- parts.push(`args:[${args}]`);
178
- }
179
-
180
- // Return value (if present and not undefined)
181
- if (entry.result !== undefined) {
182
- parts.push(`ret:${this.formatValue(entry.result)}`);
183
- }
184
-
185
- // Error (if present)
186
- if (entry.error) {
187
- parts.push(`err:${this.cleanErrorMessage(entry.error)}`);
188
- }
189
-
190
- // Calculate indentation based on depth (2 spaces base + 2 per depth level)
191
- const depth = entry.depth || 0;
192
- const indent = ' ' + ' '.repeat(depth);
193
-
194
- return `${indent}${parts.join(' ')}`;
195
- }
196
-
197
148
  /**
198
149
  * Abbreviate function name for compact output
199
150
  */
package/lib/transform.js CHANGED
@@ -30,7 +30,7 @@ export function hasExports(code) {
30
30
  /**
31
31
  * Find all exports in the source code
32
32
  * @param {string} source - Source code
33
- * @returns {Array<{name: string, type: 'function'|'const'|'class', declaration: string|null}>}
33
+ * @returns {Array<{name: string, type: 'function'|'const'|'class'|'object', declaration: string|null}>}
34
34
  */
35
35
  export function findExports(source) {
36
36
  const exports = [];
@@ -49,6 +49,16 @@ export function findExports(source) {
49
49
  exports.push({ name: match[1], type: "const", declaration: "inline" });
50
50
  }
51
51
 
52
+ // Match: export const name = { - object literal exports
53
+ // Negative lookahead to avoid matching functions/arrows already caught above
54
+ const objectExportRegex = /export\s+const\s+(\w+)\s*=\s*\{/g;
55
+ while ((match = objectExportRegex.exec(source)) !== null) {
56
+ const name = match[1];
57
+ // Skip if already found as a function/const export
58
+ if (exports.some(e => e.name === name)) continue;
59
+ exports.push({ name, type: "object", declaration: "inline" });
60
+ }
61
+
52
62
  // Match: export class ClassName
53
63
  const classExportRegex = /export\s+class\s+(\w+)/g;
54
64
  while ((match = classExportRegex.exec(source)) !== null) {
@@ -66,13 +76,14 @@ export function findExports(source) {
66
76
  const isFunction = new RegExp(`function\\s+${name}\\s*\\(`).test(source) ||
67
77
  new RegExp(`(const|let|var)\\s+${name}\\s*=\\s*(async\\s*)?\\(`).test(source) ||
68
78
  new RegExp(`(const|let|var)\\s+${name}\\s*=\\s*(async\\s+)?function`).test(source);
79
+ const isObject = new RegExp(`(const|let|var)\\s+${name}\\s*=\\s*\\{`).test(source);
69
80
 
70
81
  // Skip if already found as inline export
71
82
  if (exports.some(e => e.name === name)) continue;
72
83
 
73
84
  exports.push({
74
85
  name,
75
- type: isClass ? "class" : isFunction ? "function" : "unknown",
86
+ type: isClass ? "class" : isFunction ? "function" : isObject ? "object" : "unknown",
76
87
  declaration: "named"
77
88
  });
78
89
  }
@@ -134,6 +145,10 @@ export function transformSource(source, moduleNameOrOptions, tracerImportPath) {
134
145
  import { getGlobalReporter as __taist_getReporter } from "${reporterPath}";
135
146
  import { getContext as __taist_getContext, runWithContext as __taist_runWithContext, generateId as __taist_generateId } from "${traceContextPath}";
136
147
  const __taist_reporter = __taist_getReporter();
148
+ // Eagerly connect to collector if socket path is set (build-time instrumentation)
149
+ if (process.env.TAIST_COLLECTOR_SOCKET && !__taist_reporter.isConnected()) {
150
+ __taist_reporter.connectEager();
151
+ }
137
152
  const __taist_wrap = (fn, name) => {
138
153
  if (typeof fn !== 'function') return fn;
139
154
  const wrapped = function(...args) {
@@ -201,6 +216,19 @@ const __taist_instrumentClass = (cls, name) => {
201
216
  }
202
217
  return cls;
203
218
  };
219
+ const __taist_instrumentObject = (obj, name, visited = new WeakSet()) => {
220
+ if (!obj || typeof obj !== 'object' || visited.has(obj)) return obj;
221
+ visited.add(obj);
222
+ for (const key of Object.keys(obj)) {
223
+ const value = obj[key];
224
+ if (typeof value === 'function') {
225
+ obj[key] = __taist_wrap(value, name + '.' + key);
226
+ } else if (value && typeof value === 'object' && !Array.isArray(value)) {
227
+ __taist_instrumentObject(value, name + '.' + key, visited);
228
+ }
229
+ }
230
+ return obj;
231
+ };
204
232
  const __taist_module = "${moduleName}";
205
233
  // --- END TAIST ---
206
234
 
@@ -223,6 +251,20 @@ const __taist_instrumentClass = (cls, name) => {
223
251
  __taist.instrument(cls, name);
224
252
  return cls;
225
253
  };
254
+ const __taist_instrumentObject = (obj, name, visited = new WeakSet()) => {
255
+ if (!__taist?.options?.enabled) return obj;
256
+ if (!obj || typeof obj !== 'object' || visited.has(obj)) return obj;
257
+ visited.add(obj);
258
+ for (const key of Object.keys(obj)) {
259
+ const value = obj[key];
260
+ if (typeof value === 'function') {
261
+ obj[key] = __taist_wrap(value, name + '.' + key);
262
+ } else if (value && typeof value === 'object' && !Array.isArray(value)) {
263
+ __taist_instrumentObject(value, name + '.' + key, visited);
264
+ }
265
+ }
266
+ return obj;
267
+ };
226
268
  const __taist_module = "${moduleName}";
227
269
  // --- END TAIST ---
228
270
 
@@ -242,9 +284,10 @@ const __taist_module = "${moduleName}";
242
284
 
243
285
  let transformed = shebang + injection + sourceWithoutShebang;
244
286
 
245
- // Separate classes from functions - classes need different handling to preserve hoisting
287
+ // Separate exports by type - each needs different handling
246
288
  const classExports = exports.filter(e => e.type === "class");
247
- const functionExports = exports.filter(e => e.type !== "class");
289
+ const objectExports = exports.filter(e => e.type === "object");
290
+ const functionExports = exports.filter(e => e.type !== "class" && e.type !== "object");
248
291
 
249
292
  // For CLASSES: Keep original export, instrument in-place (preserves hoisting)
250
293
  // This avoids TDZ issues with circular dependencies
@@ -351,6 +394,15 @@ const __taist_module = "${moduleName}";
351
394
  })
352
395
  .join("\n");
353
396
 
397
+ // Add in-place instrumentation for OBJECT LITERALS (wraps nested methods)
398
+ // __taist_instrumentObject recursively wraps function properties
399
+ const objectInstrumentations = objectExports
400
+ .map((exp) => {
401
+ const nameExpr = `(__taist_module === "${exp.name}" ? "${exp.name}" : __taist_module + ".${exp.name}")`;
402
+ return `__taist_instrumentObject(${exp.name}, ${nameExpr});`;
403
+ })
404
+ .join("\n");
405
+
354
406
  transformed += `\n\n// --- TAIST INSTRUMENTATION ---\n`;
355
407
 
356
408
  if (functionReexports) {
@@ -361,6 +413,10 @@ const __taist_module = "${moduleName}";
361
413
  transformed += `// In-place class instrumentation (preserves hoisting)\n${classInstrumentations}\n`;
362
414
  }
363
415
 
416
+ if (objectInstrumentations) {
417
+ transformed += `// In-place object literal instrumentation (wraps nested methods)\n${objectInstrumentations}\n`;
418
+ }
419
+
364
420
  // Add default exports at the end (after the wrapped versions are defined)
365
421
  for (const name of defaultExports) {
366
422
  transformed += `export default ${name};\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taist",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Token-Optimized Testing Framework for AI-Assisted Development",
5
5
  "main": "index.js",
6
6
  "type": "module",