File size: 5,785 Bytes
a6b96c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"use strict";
/**
 * Fallow binary resolution and report normalisation.
 *
 * ADR-457 build-at-publish: the hand-written bin/lib/fallow-runner.cjs
 * collapsed to a TypeScript source of truth. Behaviour is preserved
 * byte-for-behaviour from the prior hand-written .cjs; only types are added.
 *
 * Parses the real fallow `audit --format json` schema (schema_version 3
 * envelope, nested dead_code/duplication sections). See fallow 2.70.0+.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveFallowBinary = resolveFallowBinary;
exports.requireFallowBinary = requireFallowBinary;
exports.normalizeFallowReport = normalizeFallowReport;
exports.normalizeFallowReportFile = normalizeFallowReportFile;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
function candidateNames() {
    return process.platform === 'win32'
        ? ['fallow.exe', 'fallow.cmd', 'fallow.bat', 'fallow']
        : ['fallow'];
}
function isExecutableFile(filePath) {
    try {
        const stat = node_fs_1.default.statSync(filePath);
        if (!stat.isFile())
            return false;
        if (process.platform === 'win32')
            return true;
        node_fs_1.default.accessSync(filePath, node_fs_1.default.constants.X_OK);
        return true;
    }
    catch {
        return false;
    }
}
function findInPath(envPath) {
    if (!envPath)
        return null;
    const names = candidateNames();
    const segments = envPath.split(node_path_1.default.delimiter).filter(Boolean);
    for (const segment of segments) {
        for (const name of names) {
            const candidate = node_path_1.default.join(segment, name);
            if (isExecutableFile(candidate))
                return candidate;
        }
    }
    return null;
}
function findInNodeModules(cwd) {
    const names = candidateNames();
    const binDir = node_path_1.default.join(cwd, 'node_modules', '.bin');
    for (const name of names) {
        const candidate = node_path_1.default.join(binDir, name);
        if (isExecutableFile(candidate))
            return candidate;
    }
    return null;
}
function resolveFallowBinary({ cwd, envPath = process.env['PATH'] ?? '' }) {
    return findInNodeModules(cwd) || findInPath(envPath) || null;
}
function requireFallowBinary({ cwd, envPath = process.env['PATH'] ?? '' }) {
    const binary = resolveFallowBinary({ cwd, envPath });
    if (binary)
        return binary;
    throw new Error('Fallow is enabled but no binary was found. Please install fallow via `npm install -D fallow` or `cargo install fallow`.');
}
function normalizeFallowReport(report) {
    const deadCodeRaw = report?.dead_code;
    const duplicationRaw = report?.duplication;
    const unusedExports = (Array.isArray(deadCodeRaw?.unused_exports)
        ? (deadCodeRaw?.unused_exports ?? [])
        : []).filter((x) => x !== null && typeof x === 'object');
    const unusedFiles = (Array.isArray(deadCodeRaw?.unused_files)
        ? (deadCodeRaw?.unused_files ?? [])
        : []).filter((x) => x !== null && typeof x === 'object');
    const circularDeps = (Array.isArray(deadCodeRaw?.circular_dependencies)
        ? (deadCodeRaw?.circular_dependencies ?? [])
        : []).filter((x) => x !== null && typeof x === 'object');
    const cloneGroups = (Array.isArray(duplicationRaw?.clone_groups)
        ? (duplicationRaw?.clone_groups ?? [])
        : []).filter((x) => x !== null && typeof x === 'object');
    const findings = [];
    for (const item of unusedExports) {
        if (!item || typeof item !== 'object')
            continue;
        findings.push({
            type: 'unused_export',
            message: `Unused export ${item.export_name ?? '<unknown>'}`,
            file: item.path ?? '',
            line: item.line ?? null,
        });
    }
    for (const item of unusedFiles) {
        if (!item || typeof item !== 'object')
            continue;
        findings.push({
            type: 'unused_file',
            message: `Unused file ${item.path ?? '<unknown>'}`,
            file: item.path ?? '',
            line: null,
        });
    }
    for (const item of circularDeps) {
        if (!item || typeof item !== 'object')
            continue;
        const files = Array.isArray(item.files) ? item.files : [];
        findings.push({
            type: 'circular_dependency',
            message: `Circular dependency: ${files.join(' -> ')}`,
            file: files.length > 0 ? files[0] : '',
            line: item.line ?? null,
        });
    }
    for (const group of cloneGroups) {
        if (!group || typeof group !== 'object')
            continue;
        const instances = Array.isArray(group.instances) ? group.instances : [];
        findings.push({
            type: 'duplicate_block',
            message: `Duplicate block (${instances.length} instances)`,
            file: instances[0]?.file ?? '',
            line: instances[0]?.start_line ?? null,
            related_file: instances[1]?.file ?? '',
        });
    }
    return {
        summary: {
            unused_exports: unusedExports.length,
            unused_files: unusedFiles.length,
            duplicates: cloneGroups.length,
            circular_dependencies: circularDeps.length,
            total: findings.length,
        },
        findings,
    };
}
function normalizeFallowReportFile(filePath) {
    try {
        const raw = node_fs_1.default.readFileSync(filePath, 'utf8');
        const parsed = JSON.parse(raw);
        return normalizeFallowReport(parsed);
    }
    catch {
        return normalizeFallowReport(null);
    }
}