mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-01 05:38:33 +08:00
fix: Harden mutation picker against low-value source files (#31914)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,7 @@
|
||||
*/
|
||||
|
||||
import { readdir, readFile } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
|
||||
@@ -65,20 +65,54 @@ function parseArgs(argv) {
|
||||
}
|
||||
|
||||
// Files with no useful mutation surface: barrels, declarations, type-only modules.
|
||||
const LOW_VALUE_BASENAMES = new Set(['interfaces', 'index', 'constants', 'types']);
|
||||
// Matched against either the full basename (`types.ts`) or the trailing
|
||||
// dot-segment (`foo.types.ts` → `types`), so dotted-suffix declaration files
|
||||
// are caught the same as their plain-named counterparts. `methods` covers
|
||||
// NativeDoc `*.methods.ts` descriptor files; `schemas` covers pure Zod
|
||||
// declaration files; `message-event-bus` is an exact-basename entry for a
|
||||
// bulk enum + interfaces + Zod module with no function logic.
|
||||
const LOW_VALUE_BASENAMES = new Set([
|
||||
'interfaces',
|
||||
'index',
|
||||
'constants',
|
||||
'types',
|
||||
'methods',
|
||||
'schemas',
|
||||
'message-event-bus',
|
||||
]);
|
||||
|
||||
// Files with fewer than this many non-blank, non-import lines have so little
|
||||
// surface area they're not worth mutating — e.g. trivial error subclasses with
|
||||
// empty bodies or one hardcoded super() call. This catches what an exact-name
|
||||
// list can't (`error` can't be skipped wholesale because files like
|
||||
// `workflow-activation.error.ts` have real branching logic).
|
||||
const MIN_MEANINGFUL_LINES = 15;
|
||||
|
||||
// Directories whose contents are tests/fixtures/mocks, not production code.
|
||||
// Pruned in `walkSources` so we don't descend; `isMutationWorthy` re-checks as a
|
||||
// safety net for packages that co-locate tests as siblings (e.g. `foo.test.ts`).
|
||||
const NON_SOURCE_DIRS = new Set(['__tests__', '__mocks__', 'fixtures']);
|
||||
|
||||
function countMeaningfulLines(content) {
|
||||
let count = 0;
|
||||
for (const raw of content.split('\n')) {
|
||||
const line = raw.trim();
|
||||
if (!line) continue;
|
||||
if (line.startsWith('import ')) continue;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function isMutationWorthy(absPath) {
|
||||
if (absPath.endsWith('.d.ts')) return false;
|
||||
if (/\.(test|spec)\.ts$/.test(absPath)) return false;
|
||||
if (absPath.includes(`${path.sep}__tests__${path.sep}`)) return false;
|
||||
if (absPath.includes(`${path.sep}__mocks__${path.sep}`)) return false;
|
||||
const base = path.basename(absPath, '.ts');
|
||||
if (LOW_VALUE_BASENAMES.has(base)) return false;
|
||||
const lastSegment = base.split('.').at(-1);
|
||||
if (LOW_VALUE_BASENAMES.has(base) || LOW_VALUE_BASENAMES.has(lastSegment)) return false;
|
||||
if (countMeaningfulLines(readFileSync(absPath, 'utf8')) < MIN_MEANINGFUL_LINES) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user