Well gentlemen, I managed to pull this off. Here are the notes for my future self, or anyone else:
- Clear out unused imports across the project using IntelliJ’s Elm plugin:

- Use the following script to clear out unused modules, including the resulting empty folders.
import * as fs from 'fs-extra';
import * as path from 'path';
export {
removeUnusedElmModules,
}
function removeUnusedElmModules(
directory = 'src/scripts',
entryModuleName = 'MainModule',
excludeDirectories = [
'src/scripts/Api',
'src/scripts/ElmFramework',
'review/src',
],
): void {
const allElmPaths = findFiles(directory, excludeDirectories, '.elm');
const allUsedElmPaths = getUsedElmPaths(directory, entryModuleName);
const allUnusedElmPaths = excludeMembers(allElmPaths, allUsedElmPaths);
removeFiles(allUnusedElmPaths);
removeEmptyDirectories(directory);
}
function getUsedElmPaths(directoryPath: string, entryModuleName: string): string[] {
return getUsedModules(directoryPath, entryModuleName)
.map(moduleName => getModulePath(directoryPath, moduleName));
function getUsedModules(baseDir: string, entryModuleName: string): string[] {
const visitedModules = exploreModule(baseDir, new Set(), entryModuleName);
return Array.from(visitedModules);
}
function exploreModule(baseDir: string, visitedModules: Set<string>, moduleName: string): Set<string> {
if (visitedModules.has(moduleName)) return visitedModules;
const modulePath = getModulePath(baseDir, moduleName);
if (!fs.existsSync(modulePath)) return visitedModules;
visitedModules.add(moduleName);
const content = readModuleContent(modulePath);
const imports = extractImports(content);
imports.forEach((importName) => exploreModule(baseDir, visitedModules, importName));
return visitedModules;
}
function readModuleContent(modulePath: string): string {
return fs.readFileSync(modulePath, 'utf-8');
}
function extractImports(content: string): string[] {
return (content.match(/^import\s+([^\s]+)/gm) || []).map((line) => line.split(' ')[1]);
}
function getModulePath(baseDir: string, moduleName: string): string {
return path.join(baseDir, moduleName.replace(/\./g, '/') + '.elm');
}
}
function removeFiles(filePaths: string[]): void {
filePaths.forEach(filePath => {
try {
fs.unlinkSync(filePath);
} catch (error) {
console.error(`Failed to delete ${filePath}:`, error);
}
});
}
function excludeMembers(toChange: string[], excludeThese: string[]): string[] {
const setB = new Set(excludeThese);
return toChange.filter(item => !setB.has(item));
}
function findFiles(directory: string, excludedPaths: string[], extension: string): string[] {
return excludedPaths.includes(directory)
? []
: fs.readdirSync(directory).flatMap(decider);
function decider(candidate: string): string[] {
const fullPath = path.join(directory, candidate);
const stat = fs.statSync(fullPath);
return stat.isDirectory()
? findFiles(fullPath, excludedPaths, extension)
: path.extname(candidate) === extension
? [fullPath]
: [];
}
}
function removeEmptyDirectories(directory: string): void {
const files = fs.readdirSync(directory);
files.forEach(file => {
const fullPath = path.join(directory, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
removeEmptyDirectories(fullPath);
}
});
if (fs.readdirSync(directory).length === 0) {
fs.rmdirSync(directory);
}
}
Jeroen, I’ll try to reach out in a couple of days to see if you can debug the elm-review issues.
Cheers!