>>108264667
Here's a solution with no mutations or side effects.
/** @typedef {Record<string, any>} Tree */
/** @typedef {ReadonlyArray<string>} Path */
const paths = [
"src/index.js",
"src/utils/helpers.js",
"src/utils/math/add.js",
"public/index.html",
"README.md",
];
const toPathArray = (/** @type {string} */ str) => str.split("/");
const pathToBranch = (
/** @type {Path} */ path,
/** @type {Tree | undefined} */ branch = undefined,
) => {
const pathSegment = path.at(-1);
if (!pathSegment) {
if (!branch) throw new Error("Invalid params.");
return branch;
}
if (!branch) {
return pathToBranch(path.slice(0, path.length - 1), {
[pathSegment]: null,
});
}
return pathToBranch(path.slice(0, path.length - 1), {
[pathSegment]: branch,
});
};
const toMergedBranch = (
/** @type {Tree} */ tree,
/** @type {Tree} */ branch,
/** @type {Path} */ path = [],
) => {
if (!branch) return pathToBranch(path, tree);
if (!tree) return pathToBranch(path, branch);
const pathSegment = Object.keys(branch).at(-1);
return toMergedBranch(
tree[pathSegment],
branch[pathSegment],
path.concat(pathSegment),
);
};
const mergeBranchReducer = (
/** @type {Tree} */ accumulatedTree,
/** @type {Tree} */ currentBranch,
) =>
Object.assign(
structuredClone(accumulatedTree),
toMergedBranch(accumulatedTree, currentBranch),
);
const toFileSystem = (/** @type {string[]} */ path) =>
path
.map(toPathArray)
.map((p) => pathToBranch(p))
.reduce(mergeBranchReducer, {});
(technically Object.assign() mutates the first param, but it's an anonymous clone inside the method's scope so it's fine)