MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / proposeParameterizedHelper

Function proposeParameterizedHelper

src/codegraph/helper-extract.ts:236–294  ·  view source on GitHub ↗
(members: FunctionUnit[], helperName = 'extractedHelper')

Source from the content-addressed store, hash-verified

234 * rather than proposing something wrong. PURE.
235 */
236export function proposeParameterizedHelper(members: FunctionUnit[], helperName = 'extractedHelper'): ParamProposal {
237 const decline = (reason: string): ParamProposal => ({ ok: false, reason, helperName, params: [], sketch: '', calls: [] });
238 if (members.length < 2) return decline('need at least two functions');
239
240 // Neutralize each function's own name, and strip comments BEFORE aligning: two bodies that
241 // differ only in comments are the same code (dogfooding caught 100%-similar pairs declining
242 // as "structurally divergent" purely because their comments tokenized differently).
243 let bodies = members.map(m => stripComments(m.body).replace(new RegExp(`\\b${m.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'g'), helperName));
244 let toks = bodies.map(b => rawTokens(b));
245 let dropped: string[] | undefined;
246 if (toks.some(t => t.length !== toks[0]!.length)) {
247 // Mixed structural variants in one cluster (e.g. a Number family + a BigInt family): keep the
248 // LARGEST same-token-count subset and report the rest as dropped, instead of declining outright.
249 const byCount = new Map<number, number[]>();
250 toks.forEach((t, i) => { const k = t.length; if (!byCount.has(k)) byCount.set(k, []); byCount.get(k)!.push(i); });
251 const best = [...byCount.values()].sort((a, b) => b.length - a.length)[0]!;
252 if (best.length < 2) return decline('bodies do not align structurally (token counts differ) — too divergent to parameterize mechanically');
253 dropped = members.filter((_, i) => !best.includes(i)).map(m => m.name);
254 members = best.map(i => members[i]!);
255 bodies = best.map(i => bodies[i]!);
256 toks = best.map(i => toks[i]!);
257 }
258
259 // Positions where members disagree, grouped by their value-vector (co-varying positions = one param).
260 const n = toks[0]!.length;
261 const groups = new Map<string, number[]>();
262 for (let p = 0; p < n; p++) {
263 const values = toks.map(t => t[p]!.text);
264 if (values.every(v => v === values[0])) continue;
265 const key = JSON.stringify(values);
266 if (!groups.has(key)) groups.set(key, []);
267 groups.get(key)!.push(p);
268 }
269 if (groups.size === 0) return decline('bodies are identical — use consolidate-dupes, no parameter needed');
270 if (groups.size > 4) return decline(`${groups.size} independently-varying parts — too many to parameterize cleanly`);
271
272 // Name each parameter from context when possible (`kind: "min"` → param `kind`), else p1/p2….
273 const used = new Set<string>();
274 const params: { name: string; values: string[]; positions: number[] }[] = [];
275 for (const [key, positions] of groups) {
276 const first = positions[0]!;
277 const ref = toks[0]!;
278 let name = ref[first - 1]?.text === ':' && /^[A-Za-z_$][\w$]*$/.test(ref[first - 2]?.text ?? '') ? ref[first - 2]!.text : `p${params.length + 1}`;
279 while (used.has(name)) name = `${name}_`;
280 used.add(name);
281 params.push({ name, values: JSON.parse(key), positions });
282 }
283
284 // Sketch: reference body with each varying token replaced by its parameter name (right-to-left).
285 let sketch = bodies[0]!;
286 const subs = params.flatMap(p => p.positions.map(pos => ({ pos, name: p.name })))
287 .sort((a, b) => toks[0]![b.pos]!.start - toks[0]![a.pos]!.start);
288 for (const s of subs) {
289 const t = toks[0]![s.pos]!;
290 sketch = sketch.slice(0, t.start) + s.name + sketch.slice(t.start + t.text.length);
291 }
292 const calls = members.map((m, i) => ({ member: m.name, args: params.map(p => p.values[i]!) }));
293 return { ok: true, helperName, params: params.map(({ name, values }) => ({ name, values })), sketch, calls, dropped };

Callers 2

executeMethod · 0.85

Calls 8

declineFunction · 0.85
stripCommentsFunction · 0.85
rawTokensFunction · 0.85
addMethod · 0.80
hasMethod · 0.45
setMethod · 0.45
pushMethod · 0.45
getMethod · 0.45

Tested by

no test coverage detected