@sitrozyi/repomix-semantic-compressor 0.1.2

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/src/ast.mjs ADDED
@@ -0,0 +1,489 @@
1
+ import { parse } from '@babel/parser';
2
+ import traversePkg from '@babel/traverse';
3
+ import generatePkg from '@babel/generator';
4
+
5
+ const traverse = traversePkg.default || traversePkg;
6
+ const generate = generatePkg.default || generatePkg;
7
+
8
+ function getPayloadPropName(memPath) {
9
+ const node = memPath.node;
10
+ if (!node) return null;
11
+ const obj = node.object;
12
+ const prop = node.property;
13
+
14
+ if (!prop || prop.type !== 'Identifier') return null;
15
+ if (['type', 'action', 'role'].includes(prop.name)) return null;
16
+
17
+ // Handles payload.userId and payload?.userId
18
+ if (obj && obj.type === 'Identifier' && ['payload', 'data', 'event'].includes(obj.name)) {
19
+ return prop.name;
20
+ }
21
+
22
+ // Handles action.payload.userId and action?.payload?.userId
23
+ if (
24
+ obj &&
25
+ (obj.type === 'MemberExpression' || obj.type === 'OptionalMemberExpression') &&
26
+ obj.property &&
27
+ obj.property.type === 'Identifier' &&
28
+ ['payload', 'data'].includes(obj.property.name)
29
+ ) {
30
+ return prop.name;
31
+ }
32
+
33
+ return null;
34
+ }
35
+
36
+ function collectDestructuredProps(pattern, targetSet) {
37
+ if (!pattern) return;
38
+ if (pattern.type === 'ObjectPattern') {
39
+ for (const prop of pattern.properties) {
40
+ if (prop.type === 'ObjectProperty') {
41
+ if (prop.value.type === 'Identifier') {
42
+ targetSet.add(prop.value.name);
43
+ } else if (prop.value.type === 'ObjectPattern' || prop.value.type === 'ArrayPattern') {
44
+ collectDestructuredProps(prop.value, targetSet);
45
+ } else if (prop.value.type === 'AssignmentPattern') {
46
+ if (prop.value.left.type === 'Identifier') {
47
+ targetSet.add(prop.value.left.name);
48
+ } else {
49
+ collectDestructuredProps(prop.value.left, targetSet);
50
+ }
51
+ } else if (prop.key && prop.key.type === 'Identifier') {
52
+ targetSet.add(prop.key.name);
53
+ }
54
+ } else if (prop.type === 'RestElement' && prop.argument && prop.argument.type === 'Identifier') {
55
+ targetSet.add(`...${prop.argument.name}`);
56
+ }
57
+ }
58
+ } else if (pattern.type === 'ArrayPattern') {
59
+ for (const elem of pattern.elements) {
60
+ if (!elem) continue;
61
+ if (elem.type === 'Identifier') {
62
+ targetSet.add(elem.name);
63
+ } else if (elem.type === 'ObjectPattern' || elem.type === 'ArrayPattern') {
64
+ collectDestructuredProps(elem, targetSet);
65
+ } else if (elem.type === 'RestElement' && elem.argument && elem.argument.type === 'Identifier') {
66
+ targetSet.add(`...${elem.argument.name}`);
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ export function extractProtocolsFromAST(astPath) {
73
+ const protocols = new Set();
74
+
75
+ astPath.traverse({
76
+ SwitchCase(casePath) {
77
+ if (casePath.node.test && casePath.node.test.type === 'StringLiteral') {
78
+ const actionName = casePath.node.test.value;
79
+ const payloadProps = new Set();
80
+
81
+ casePath.traverse({
82
+ 'MemberExpression|OptionalMemberExpression'(memPath) {
83
+ const propName = getPayloadPropName(memPath);
84
+ if (propName) payloadProps.add(propName);
85
+ },
86
+ VariableDeclarator(varPath) {
87
+ const init = varPath.node.init;
88
+ if (!init) return;
89
+
90
+ const isPayloadSource =
91
+ (init.type === 'Identifier' && ['payload', 'data', 'event'].includes(init.name)) ||
92
+ ((init.type === 'MemberExpression' || init.type === 'OptionalMemberExpression') &&
93
+ init.property &&
94
+ init.property.type === 'Identifier' &&
95
+ ['payload', 'data'].includes(init.property.name));
96
+
97
+ if (isPayloadSource && varPath.node.id) {
98
+ collectDestructuredProps(varPath.node.id, payloadProps);
99
+ }
100
+ }
101
+ });
102
+
103
+ if (payloadProps.size > 0) {
104
+ protocols.add(`${actionName}(${Array.from(payloadProps).join(', ')})`);
105
+ } else {
106
+ protocols.add(actionName);
107
+ }
108
+ }
109
+ },
110
+ CallExpression(callPath) {
111
+ const callee = callPath.node.callee;
112
+ let funcName = null;
113
+
114
+ // Standalone functions: dispatch(...)
115
+ if (callee.type === 'Identifier') {
116
+ funcName = callee.name;
117
+ }
118
+ // Method calls: emitter.emit(...) / store.dispatch(...)
119
+ else if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
120
+ funcName = callee.property.name;
121
+ }
122
+
123
+ if (funcName && ['emit', 'dispatch', 'trigger', 'send'].includes(funcName)) {
124
+ const firstArg = callPath.node.arguments[0];
125
+ if (!firstArg) return;
126
+
127
+ // Pattern: emitter.emit('EVENT_NAME')
128
+ if (firstArg.type === 'StringLiteral') {
129
+ protocols.add(`emit:${firstArg.value}`);
130
+ }
131
+ // Pattern: dispatch({ type: 'ACTION_TYPE' })
132
+ else if (firstArg.type === 'ObjectExpression') {
133
+ const typeProp = firstArg.properties.find(
134
+ (p) =>
135
+ p.type === 'ObjectProperty' &&
136
+ ((p.key.type === 'Identifier' && p.key.name === 'type') ||
137
+ (p.key.type === 'StringLiteral' && p.key.value === 'type')) &&
138
+ p.value.type === 'StringLiteral'
139
+ );
140
+ if (typeProp) {
141
+ protocols.add(`dispatch:${typeProp.value.value}`);
142
+ }
143
+ }
144
+ }
145
+ },
146
+ BinaryExpression(binPath) {
147
+ if (['===', '=='].includes(binPath.node.operator)) {
148
+ let stringVal = null;
149
+ if (binPath.node.right.type === 'StringLiteral') stringVal = binPath.node.right.value;
150
+ if (binPath.node.left.type === 'StringLiteral') stringVal = binPath.node.left.value;
151
+
152
+ if (stringVal && /^[A-Z0-9_-]{3,}$/.test(stringVal)) {
153
+ protocols.add(stringVal);
154
+ }
155
+ }
156
+ }
157
+ });
158
+
159
+ return Array.from(protocols);
160
+ }
161
+
162
+ const CORE_LOGIC_REGEX = /^#?(is|has|can|should|calc|calculate|validate|check|parse|format|sanitize)[A-Z0-9_]/;
163
+
164
+ function isHookCall(callNode) {
165
+ if (!callNode || callNode.type !== 'CallExpression') return false;
166
+ const callee = callNode.callee;
167
+ if (callee.type === 'Identifier') {
168
+ return /^use[A-Z0-9_]/.test(callee.name);
169
+ }
170
+ if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
171
+ return /^use[A-Z0-9_]/.test(callee.property.name);
172
+ }
173
+ return false;
174
+ }
175
+
176
+ function isHookStatement(stmt) {
177
+ if (!stmt) return false;
178
+ if (stmt.type === 'ExpressionStatement' && isHookCall(stmt.expression)) {
179
+ return true;
180
+ }
181
+ if (stmt.type === 'VariableDeclaration') {
182
+ return stmt.declarations.some((decl) => decl.init && isHookCall(decl.init));
183
+ }
184
+ return false;
185
+ }
186
+
187
+ function isHookCallback(astPath) {
188
+ if (!astPath.parentPath) return false;
189
+ const parent = astPath.parentPath.node;
190
+ if (parent.type === 'CallExpression' && isHookCall(parent)) {
191
+ return true;
192
+ }
193
+ return false;
194
+ }
195
+
196
+ function simplifyHookCall(call) {
197
+ if (!call || call.type !== 'CallExpression') return call;
198
+ const calleeName = call.callee.type === 'Identifier' ? call.callee.name : call.callee.property?.name || '';
199
+ if (['useEffect', 'useLayoutEffect', 'useInsertionEffect', 'useCallback', 'useMemo'].includes(calleeName)) {
200
+ if (call.arguments.length > 0) {
201
+ const firstArg = call.arguments[0];
202
+ if (['ArrowFunctionExpression', 'FunctionExpression'].includes(firstArg.type)) {
203
+ firstArg.body = {
204
+ type: 'BlockStatement',
205
+ body: []
206
+ };
207
+ firstArg.expression = false;
208
+ delete firstArg.leadingComments;
209
+ delete firstArg.innerComments;
210
+ delete firstArg.trailingComments;
211
+ }
212
+ }
213
+ }
214
+ return call;
215
+ }
216
+
217
+ function simplifyHookStatement(stmt) {
218
+ if (!stmt) return stmt;
219
+ if (stmt.type === 'ExpressionStatement' && isHookCall(stmt.expression)) {
220
+ simplifyHookCall(stmt.expression);
221
+ } else if (stmt.type === 'VariableDeclaration') {
222
+ for (const decl of stmt.declarations) {
223
+ if (decl.init && isHookCall(decl.init)) {
224
+ simplifyHookCall(decl.init);
225
+ }
226
+ }
227
+ }
228
+ return stmt;
229
+ }
230
+
231
+ function getFunctionName(astPath) {
232
+ const node = astPath.node;
233
+ if (node.id && node.id.name) return node.id.name;
234
+ if (astPath.parentPath) {
235
+ if (astPath.parentPath.isVariableDeclarator() && astPath.parentPath.node.id.type === 'Identifier') {
236
+ return astPath.parentPath.node.id.name;
237
+ }
238
+ if (astPath.parentPath.isObjectProperty() || astPath.parentPath.isClassProperty()) {
239
+ if (astPath.parentPath.node.key) {
240
+ if (astPath.parentPath.node.key.type === 'Identifier') return astPath.parentPath.node.key.name;
241
+ if (astPath.parentPath.node.key.type === 'PrivateName' && astPath.parentPath.node.key.id) {
242
+ return `#${astPath.parentPath.node.key.id.name}`;
243
+ }
244
+ }
245
+ }
246
+ }
247
+ if (node.key) {
248
+ if (node.key.type === 'Identifier') return node.key.name;
249
+ if (node.key.type === 'PrivateName' && node.key.id) return `#${node.key.id.name}`;
250
+ }
251
+ return null;
252
+ }
253
+
254
+ export function skeletonizeWithAST(code, isTypeScript, maxPreserveLines = 8, isJSX = true) {
255
+ try {
256
+ const ast = parse(code, {
257
+ sourceType: 'unambiguous',
258
+ errorRecovery: true,
259
+ plugins: [
260
+ isJSX ? 'jsx' : null,
261
+ isTypeScript ? 'typescript' : null,
262
+ ['decorators', { decoratorsBeforeExport: true }],
263
+ 'decoratorAutoAccessors',
264
+ 'explicitResourceManagement',
265
+ 'classProperties',
266
+ 'classPrivateProperties',
267
+ 'classPrivateMethods',
268
+ 'classStaticBlock',
269
+ 'dynamicImport',
270
+ 'exportDefaultFrom',
271
+ 'importAttributes'
272
+ ].filter(Boolean)
273
+ });
274
+
275
+ traverse(ast, {
276
+ JSXElement(jsxPath) {
277
+ const children = jsxPath.node.children;
278
+ if (!children || children.length < 3) return;
279
+
280
+ const newChildren = [];
281
+ let lastTagName = null;
282
+ let repeatCount = 0;
283
+ let pendingWhitespace = [];
284
+
285
+ for (const child of children) {
286
+ if (child.type === 'JSXText' && child.value.trim() === '') {
287
+ if (repeatCount > 0) {
288
+ continue;
289
+ }
290
+ pendingWhitespace.push(child);
291
+ continue;
292
+ }
293
+
294
+ if (child.type === 'JSXElement' && child.openingElement.name.type === 'JSXIdentifier') {
295
+ const tagName = child.openingElement.name.name;
296
+ if (tagName === lastTagName) {
297
+ repeatCount++;
298
+ pendingWhitespace = [];
299
+ continue;
300
+ } else {
301
+ if (repeatCount > 0) {
302
+ newChildren.push({
303
+ type: 'JSXExpressionContainer',
304
+ expression: {
305
+ type: 'JSXEmptyExpression',
306
+ innerComments: [{ type: 'CommentBlock', value: ` ...${repeatCount} repeating <${lastTagName} /> omitted... ` }]
307
+ }
308
+ });
309
+ }
310
+ if (pendingWhitespace.length > 0) {
311
+ newChildren.push(...pendingWhitespace);
312
+ pendingWhitespace = [];
313
+ }
314
+ lastTagName = tagName;
315
+ repeatCount = 0;
316
+ newChildren.push(child);
317
+ }
318
+ } else {
319
+ if (repeatCount > 0) {
320
+ newChildren.push({
321
+ type: 'JSXExpressionContainer',
322
+ expression: {
323
+ type: 'JSXEmptyExpression',
324
+ innerComments: [{ type: 'CommentBlock', value: ` ...${repeatCount} repeating <${lastTagName} /> omitted... ` }]
325
+ }
326
+ });
327
+ lastTagName = null;
328
+ repeatCount = 0;
329
+ }
330
+ if (pendingWhitespace.length > 0) {
331
+ newChildren.push(...pendingWhitespace);
332
+ pendingWhitespace = [];
333
+ }
334
+ newChildren.push(child);
335
+ }
336
+ }
337
+
338
+ if (repeatCount > 0) {
339
+ newChildren.push({
340
+ type: 'JSXExpressionContainer',
341
+ expression: {
342
+ type: 'JSXEmptyExpression',
343
+ innerComments: [{ type: 'CommentBlock', value: ` ...${repeatCount} repeating <${lastTagName} /> omitted... ` }]
344
+ }
345
+ });
346
+ }
347
+ if (pendingWhitespace.length > 0) {
348
+ newChildren.push(...pendingWhitespace);
349
+ }
350
+
351
+ jsxPath.node.children = newChildren;
352
+ }
353
+ });
354
+
355
+ traverse(ast, {
356
+ 'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression|ClassMethod|ObjectMethod|StaticBlock'(astPath) {
357
+ if (astPath.isStaticBlock()) {
358
+ const node = astPath.node;
359
+ const startLine = node.loc ? node.loc.start.line : 0;
360
+ const endLine = node.loc ? node.loc.end.line : 0;
361
+ const totalLines = endLine - startLine + 1;
362
+ if (node.loc && totalLines <= maxPreserveLines) return;
363
+ node.body = [];
364
+ node.innerComments = [{ type: 'CommentBlock', value: ` ...static block impl (${totalLines} lines)... ` }];
365
+ astPath.skip();
366
+ return;
367
+ }
368
+
369
+ // Skip direct processing of hook callbacks (handled by simplifyHookCall on the parent component)
370
+ if (isHookCallback(astPath)) {
371
+ astPath.skip();
372
+ return;
373
+ }
374
+
375
+ const node = astPath.node;
376
+ if (!node.body) return;
377
+
378
+ const startLine = node.loc ? node.loc.start.line : 0;
379
+ const endLine = node.loc ? node.loc.end.line : 0;
380
+ const totalLines = endLine - startLine + 1;
381
+
382
+ // Preserve short functions within configured threshold
383
+ if (node.loc && totalLines <= maxPreserveLines) {
384
+ return;
385
+ }
386
+
387
+ const funcName = getFunctionName(astPath);
388
+ if (funcName && CORE_LOGIC_REGEX.test(funcName)) {
389
+ return;
390
+ }
391
+ const isConstructor =
392
+ node.kind === 'constructor' ||
393
+ (astPath.isClassMethod() && node.key && node.key.type === 'Identifier' && node.key.name === 'constructor');
394
+ const isSetter = node.kind === 'set';
395
+
396
+ const protocols = extractProtocolsFromAST(astPath);
397
+ let commentText = ` ...impl (${totalLines} lines)... `;
398
+ if (protocols.length > 0) {
399
+ commentText = ` @payloads: ${protocols.join(' | ')} (truncated ${totalLines} lines) `;
400
+ }
401
+
402
+ const leading = node.leadingComments;
403
+
404
+ const hookStatements = [];
405
+ let superCallStatement = null;
406
+
407
+ if (node.body && node.body.type === 'BlockStatement' && Array.isArray(node.body.body)) {
408
+ for (const stmt of node.body.body) {
409
+ if (isConstructor && !superCallStatement) {
410
+ if (
411
+ stmt.type === 'ExpressionStatement' &&
412
+ stmt.expression &&
413
+ stmt.expression.type === 'CallExpression' &&
414
+ stmt.expression.callee.type === 'Super'
415
+ ) {
416
+ superCallStatement = stmt;
417
+ }
418
+ }
419
+ if (isHookStatement(stmt)) {
420
+ hookStatements.push(simplifyHookStatement(stmt));
421
+ }
422
+ }
423
+ }
424
+
425
+ let replacementBody;
426
+
427
+ if (isConstructor) {
428
+ const ctorBody = superCallStatement ? [superCallStatement] : [];
429
+ replacementBody = {
430
+ type: 'BlockStatement',
431
+ body: ctorBody,
432
+ innerComments: [{ type: 'CommentBlock', value: ` ...constructor impl (${totalLines} lines)... ` }]
433
+ };
434
+ } else if (isSetter) {
435
+ replacementBody = {
436
+ type: 'BlockStatement',
437
+ body: [],
438
+ innerComments: [{ type: 'CommentBlock', value: commentText }]
439
+ };
440
+ } else {
441
+ // Emit an unconditional throw so the truncated function is inferred as
442
+ // returning `never`. `never` is assignable to any declared return type
443
+ // (including Promise<T>), so TypeScript checking still passes without
444
+ // an `as any` escape that would silently defeat downstream type safety.
445
+ const stubThrow = {
446
+ type: 'ThrowStatement',
447
+ argument: {
448
+ type: 'NewExpression',
449
+ callee: { type: 'Identifier', name: 'Error' },
450
+ arguments: [
451
+ {
452
+ type: 'StringLiteral',
453
+ value: 'Implementation omitted by repomix-semantic-compressor'
454
+ }
455
+ ]
456
+ },
457
+ leadingComments: [{ type: 'CommentBlock', value: commentText }]
458
+ };
459
+
460
+ replacementBody = {
461
+ type: 'BlockStatement',
462
+ body: hookStatements.length > 0 ? [...hookStatements, stubThrow] : [stubThrow]
463
+ };
464
+ }
465
+
466
+ if (astPath.isArrowFunctionExpression() && node.body.type !== 'BlockStatement') {
467
+ node.body = replacementBody;
468
+ node.expression = false;
469
+ } else if (node.body && node.body.type === 'BlockStatement') {
470
+ node.body = replacementBody;
471
+ }
472
+
473
+ if (leading) {
474
+ node.leadingComments = leading;
475
+ }
476
+
477
+ astPath.skip();
478
+ }
479
+ });
480
+
481
+ return generate(ast, {
482
+ retainLines: false,
483
+ compact: false,
484
+ comments: true
485
+ }).code;
486
+ } catch {
487
+ return code;
488
+ }
489
+ }