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.
- package/lib/config-loader.js +6 -4
- package/lib/toon-formatter.js +0 -49
- package/lib/transform.js +60 -4
- package/package.json +1 -1
package/lib/config-loader.js
CHANGED
|
@@ -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, "
|
|
84
|
-
.replace(/\*\*/g, "
|
|
85
|
-
.replace(/\*/g, "[^/]*")
|
|
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);
|
package/lib/toon-formatter.js
CHANGED
|
@@ -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
|
|
287
|
+
// Separate exports by type - each needs different handling
|
|
246
288
|
const classExports = exports.filter(e => e.type === "class");
|
|
247
|
-
const
|
|
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`;
|