#!/usr/bin/env node /** * mutate-operators (Level B) — чистый генератор валидных операторных мутантов JS-исходника. * Текстовая замена операторов (без AST-зависимости): каждый оператор флипается на свою пару * по каждому вхождению, по одному мутанту за вхождение. Цель — НЕ исчерпать пространство, а * получить горсть валидных мутантов, чтобы тест критерия имел шанс кого-то «убить». Невалидные/ * эквивалентные мутанты (задели строку/коммент) отсеивает baseline+run в mutate-runner (выживший/ * load-error). Детерминирован (стабильный порядок), dedup, cap (ограничение времени гейта, SE-LB-13). * AST-точность — опциональное усиление (судья М4 / отдельный план). * * Операторы выбраны без capture-групп → замена n-го вхождения тривиальна и корректна. * Пары не перекрываются: /===/ не матчит '!==' (две равны), /!==/ не матчит '===' (три равны), * />=/ не матчит '=>' (стрелка = '=' затем '>'; '>=' = '>' затем '='). */ // [искать (global regex), заменить (литерал, без $-групп)]. const OPS = [ [/===/g, '!=='], [/!==/g, '==='], [/&&/g, '||'], [/\|\|/g, '&&'], [/\btrue\b/g, 'false'], [/\bfalse\b/g, 'true'], [/>=/g, '>'], [/<=/g, '<'], ]; export const MUTATION_OPERATORS = { cap: 40 }; /** Заменить ТОЛЬКО n-е вхождение global-regex на литерал; остальные оставить. */ function replaceNth(source, re, replacement, nth) { let i = -1; return source.replace(re, (m) => { i += 1; return i === nth ? replacement : m; }); } export function generateMutants(source = '') { const out = []; const seen = new Set([source]); for (const [re, rep] of OPS) { const count = (source.match(re) || []).length; for (let n = 0; n < count; n += 1) { const mutated = replaceNth(source, new RegExp(re.source, re.flags), rep, n); if (mutated === source || seen.has(mutated)) continue; seen.add(mutated); out.push({ label: `${re.source}->${rep}#${n}`, mutated }); if (out.length >= MUTATION_OPERATORS.cap) return out; } } return out; }