Files
brain/tools/mutate-operators.mjs
T

54 lines
2.6 KiB
JavaScript

#!/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;
}