@tracecode/harness 0.6.1 → 0.6.5

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.
@@ -0,0 +1,242 @@
1
+ (function initTraceCodeJavaSourceAugmentations(root) {
2
+ function escapeRegExp(value) {
3
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
4
+ }
5
+
6
+ function braceDelta(line) {
7
+ let delta = 0;
8
+ for (const ch of line) {
9
+ if (ch === '{') delta += 1;
10
+ if (ch === '}') delta -= 1;
11
+ }
12
+ return delta;
13
+ }
14
+
15
+ function collectJavaCollectionDeclarations(line) {
16
+ const collections = {
17
+ maps: [],
18
+ sets: [],
19
+ adjacencyLists: [],
20
+ };
21
+ const declarationPattern =
22
+ /\b((?:java\.util\.)?(?:HashMap|LinkedHashMap|TreeMap|Map|HashSet|LinkedHashSet|TreeSet|Set|ArrayList|LinkedList|List)\s*(?:<[^;=(){}]+>)?)\s+([A-Za-z_][A-Za-z0-9_]*)\b/g;
23
+ for (const match of line.matchAll(declarationPattern)) {
24
+ const rawType = match[1] ?? '';
25
+ const typeSource = rawType.replace(/\s+/g, '');
26
+ const name = match[2];
27
+ if (!name) continue;
28
+ if (/\b(?:HashMap|LinkedHashMap|TreeMap|Map)\b/.test(typeSource)) {
29
+ collections.maps.push(name);
30
+ } else if (/\b(?:HashSet|LinkedHashSet|TreeSet|Set)\b/.test(typeSource)) {
31
+ collections.sets.push(name);
32
+ } else if (
33
+ /\b(?:ArrayList|LinkedList|List)\b/.test(typeSource) &&
34
+ /<\s*(?:java\.util\.)?(?:List|ArrayList|LinkedList)\s*</.test(rawType)
35
+ ) {
36
+ collections.adjacencyLists.push(name);
37
+ }
38
+ }
39
+ return collections;
40
+ }
41
+
42
+ function splitFirstTopLevelJavaArgument(argsSource) {
43
+ let depth = 0;
44
+ let inString = false;
45
+ let inChar = false;
46
+ let escaped = false;
47
+ for (let index = 0; index < argsSource.length; index += 1) {
48
+ const ch = argsSource[index];
49
+ if (escaped) {
50
+ escaped = false;
51
+ continue;
52
+ }
53
+ if (ch === '\\') {
54
+ escaped = true;
55
+ continue;
56
+ }
57
+ if (inString) {
58
+ if (ch === '"') inString = false;
59
+ continue;
60
+ }
61
+ if (inChar) {
62
+ if (ch === "'") inChar = false;
63
+ continue;
64
+ }
65
+ if (ch === '"') {
66
+ inString = true;
67
+ continue;
68
+ }
69
+ if (ch === "'") {
70
+ inChar = true;
71
+ continue;
72
+ }
73
+ if (ch === '(' || ch === '[' || ch === '{' || ch === '<') depth += 1;
74
+ if (ch === ')' || ch === ']' || ch === '}' || ch === '>') depth -= 1;
75
+ if (ch === ',' && depth === 0) {
76
+ return [argsSource.slice(0, index).trim(), argsSource.slice(index + 1).trim()];
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+
82
+ function replaceJavaReceiverCall(source, receiverName, methodName, replacer) {
83
+ const callPattern = new RegExp(`\\b${escapeRegExp(receiverName)}\\.${methodName}\\(`, 'g');
84
+ let output = '';
85
+ let cursor = 0;
86
+ let match;
87
+ while ((match = callPattern.exec(source)) !== null) {
88
+ const argsStart = match.index + match[0].length;
89
+ let depth = 1;
90
+ let index = argsStart;
91
+ let inString = false;
92
+ let inChar = false;
93
+ let escaped = false;
94
+ while (index < source.length) {
95
+ const ch = source[index];
96
+ if (escaped) {
97
+ escaped = false;
98
+ index += 1;
99
+ continue;
100
+ }
101
+ if (ch === '\\') {
102
+ escaped = true;
103
+ index += 1;
104
+ continue;
105
+ }
106
+ if (inString) {
107
+ if (ch === '"') inString = false;
108
+ index += 1;
109
+ continue;
110
+ }
111
+ if (inChar) {
112
+ if (ch === "'") inChar = false;
113
+ index += 1;
114
+ continue;
115
+ }
116
+ if (ch === '"') {
117
+ inString = true;
118
+ index += 1;
119
+ continue;
120
+ }
121
+ if (ch === "'") {
122
+ inChar = true;
123
+ index += 1;
124
+ continue;
125
+ }
126
+ if (ch === '(') depth += 1;
127
+ if (ch === ')') depth -= 1;
128
+ if (depth === 0) break;
129
+ index += 1;
130
+ }
131
+ if (depth !== 0) continue;
132
+ output += source.slice(cursor, match.index);
133
+ output += replacer(source.slice(argsStart, index).trim());
134
+ cursor = index + 1;
135
+ callPattern.lastIndex = cursor;
136
+ }
137
+ return output + source.slice(cursor);
138
+ }
139
+
140
+ function augmentJavaCollectionOperations(source) {
141
+ const lines = source.split('\n');
142
+ const methodStack = [];
143
+ const methodStartPattern =
144
+ /^(\s*)(?:(?:public|private|protected|static|final|synchronized)\s+)*(?:[A-Za-z_][A-Za-z0-9_<>\[\], ?]*\s+)+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*\{\s*$/;
145
+
146
+ return lines.map((line) => {
147
+ const methodMatch = line.match(methodStartPattern);
148
+ if (methodMatch) {
149
+ methodStack.push({
150
+ depth: 1,
151
+ currentTraceLine: null,
152
+ maps: new Set(),
153
+ sets: new Set(),
154
+ adjacencyLists: new Set(),
155
+ });
156
+ return line;
157
+ }
158
+
159
+ const currentMethod = methodStack[methodStack.length - 1];
160
+ let nextLine = line;
161
+ if (!currentMethod) return nextLine;
162
+
163
+ const declarations = collectJavaCollectionDeclarations(line);
164
+ declarations.maps.forEach((name) => currentMethod.maps.add(name));
165
+ declarations.sets.forEach((name) => currentMethod.sets.add(name));
166
+ declarations.adjacencyLists.forEach((name) => currentMethod.adjacencyLists.add(name));
167
+
168
+ const traceLineMatch = line.match(/TraceHooks\.emit\("line=(\d+)(?:\s|")/);
169
+ if (traceLineMatch) {
170
+ currentMethod.currentTraceLine = Number.parseInt(traceLineMatch[1], 10);
171
+ }
172
+
173
+ const lineNumber = currentMethod.currentTraceLine;
174
+ if (lineNumber !== null) {
175
+ for (const name of currentMethod.adjacencyLists) {
176
+ const indexedAddPattern = new RegExp(
177
+ `\\b${escapeRegExp(name)}\\.get\\(([^()\\n;]+)\\)\\.add\\(([^;\\n]+)\\);`,
178
+ 'g'
179
+ );
180
+ nextLine = nextLine.replace(indexedAddPattern, (_match, indexSource, valueSource) => {
181
+ const indexExpression = String(indexSource).trim();
182
+ return `{ TraceHooks.readObjectListAtLine(${lineNumber}, "${name}", ${name}, ${indexExpression}).add(${String(valueSource).trim()}); TraceHooks.emitMutatingCallAtLine(${lineNumber}, "${name}", ${indexExpression}, "add"); TraceHooks.emitGraphAdjacencyStateAtLine(${lineNumber}, "${name}", ${name}); }`;
183
+ });
184
+
185
+ const listGetPattern = new RegExp(`\\b${escapeRegExp(name)}\\.get\\(([^()\\n;]+)\\)`, 'g');
186
+ nextLine = nextLine.replace(listGetPattern, (_match, indexSource) =>
187
+ `TraceHooks.readObjectListAtLine(${lineNumber}, "${name}", ${name}, ${String(indexSource).trim()})`
188
+ );
189
+ }
190
+
191
+ for (const name of currentMethod.maps) {
192
+ nextLine = replaceJavaReceiverCall(nextLine, name, 'containsKey', (key) =>
193
+ `TraceHooks.containsMapKeyAtLine(${lineNumber}, "${name}", ${name}, ${key})`
194
+ );
195
+ nextLine = replaceJavaReceiverCall(nextLine, name, 'get', (key) =>
196
+ `TraceHooks.readMapAtLine(${lineNumber}, "${name}", ${name}, ${key})`
197
+ );
198
+ nextLine = replaceJavaReceiverCall(nextLine, name, 'put', (argsSource) => {
199
+ const parts = splitFirstTopLevelJavaArgument(argsSource);
200
+ if (!parts) return `${name}.put(${argsSource})`;
201
+ return `TraceHooks.writeMapAtLine(${lineNumber}, "${name}", ${name}, ${parts[0]}, ${parts[1]})`;
202
+ });
203
+ }
204
+
205
+ for (const name of currentMethod.sets) {
206
+ nextLine = replaceJavaReceiverCall(nextLine, name, 'contains', (key) =>
207
+ `TraceHooks.readSetAtLine(${lineNumber}, "${name}", ${name}, ${key})`
208
+ );
209
+ nextLine = replaceJavaReceiverCall(nextLine, name, 'add', (key) =>
210
+ `TraceHooks.addSetAtLine(${lineNumber}, "${name}", ${name}, ${key})`
211
+ );
212
+ nextLine = replaceJavaReceiverCall(nextLine, name, 'remove', (key) =>
213
+ `TraceHooks.removeSetAtLine(${lineNumber}, "${name}", ${name}, ${key})`
214
+ );
215
+ }
216
+
217
+ const staleMutationPattern = /TraceHooks\.emitMutatingCallAtLine\(\d+,\s*"([A-Za-z_][A-Za-z0-9_]*)",\s*"(?:put|add|remove)"\);\s*/g;
218
+ nextLine = nextLine.replace(staleMutationPattern, (match, name) =>
219
+ currentMethod.maps.has(name) || currentMethod.sets.has(name) || currentMethod.adjacencyLists.has(name) ? '' : match
220
+ );
221
+ }
222
+
223
+ currentMethod.depth += braceDelta(nextLine);
224
+ while (methodStack.length > 0 && methodStack[methodStack.length - 1].depth <= 0) {
225
+ methodStack.pop();
226
+ }
227
+ return nextLine;
228
+ }).join('\n');
229
+ }
230
+
231
+ const api = {
232
+ augmentJavaCollectionOperations,
233
+ };
234
+
235
+ root.TraceCodeJavaSourceAugmentations = api;
236
+ if (root.self && typeof root.self === 'object') {
237
+ root.self.TraceCodeJavaSourceAugmentations = api;
238
+ }
239
+ if (typeof module === 'object' && module.exports) {
240
+ module.exports = api;
241
+ }
242
+ })(typeof globalThis !== 'undefined' ? globalThis : this);