Skip to content

Commit a30448e

Browse files
betegonclaude
andcommitted
fix(init): resolve biome lint and formatting errors in listDir
Extract toDirEntry, isInsideSkippedDir, and isEscapingSymlink helpers to reduce cognitive complexity below threshold. Fix block statement and formatting violations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f79af80 commit a30448e

File tree

1 file changed

+62
-29
lines changed

1 file changed

+62
-29
lines changed

src/lib/init/local-ops.ts

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,62 @@ export async function handleLocalOp(
349349
/** Directory names that are listed at their level but never recursed into. */
350350
const SKIP_DIRS = new Set(["node_modules"]);
351351

352+
/**
353+
* Check whether an entry is inside a hidden dir or node_modules.
354+
* Top-level skip-dirs (relFromTarget === "") are still listed.
355+
*/
356+
function isInsideSkippedDir(relFromTarget: string): boolean {
357+
if (relFromTarget === "") {
358+
return false;
359+
}
360+
const segments = relFromTarget.split(path.sep);
361+
return segments.some((s) => s.startsWith(".") || SKIP_DIRS.has(s));
362+
}
363+
364+
/** Return true when a symlink resolves to a path outside `cwd`. */
365+
function isEscapingSymlink(
366+
entry: fs.Dirent,
367+
cwd: string,
368+
relPath: string
369+
): boolean {
370+
if (!entry.isSymbolicLink()) {
371+
return false;
372+
}
373+
try {
374+
safePath(cwd, relPath);
375+
return false;
376+
} catch {
377+
return true;
378+
}
379+
}
380+
381+
/** Convert a Dirent to a DirEntry, or return null if it should be skipped. */
382+
function toDirEntry(
383+
entry: fs.Dirent,
384+
cwd: string,
385+
targetPath: string,
386+
maxDepth: number
387+
): DirEntry | null {
388+
const relFromTarget = path.relative(targetPath, entry.parentPath);
389+
const depth = relFromTarget === "" ? 0 : relFromTarget.split(path.sep).length;
390+
391+
if (depth > maxDepth) {
392+
return null;
393+
}
394+
if (isInsideSkippedDir(relFromTarget)) {
395+
return null;
396+
}
397+
398+
const relPath = path.relative(cwd, path.join(entry.parentPath, entry.name));
399+
400+
if (isEscapingSymlink(entry, cwd, relPath)) {
401+
return null;
402+
}
403+
404+
const type = entry.isDirectory() ? "directory" : "file";
405+
return { name: entry.name, path: relPath, type };
406+
}
407+
352408
async function listDir(payload: ListDirPayload): Promise<LocalOpResult> {
353409
const { cwd, params } = payload;
354410
const targetPath = safePath(cwd, params.path);
@@ -364,37 +420,14 @@ async function listDir(payload: ListDirPayload): Promise<LocalOpResult> {
364420
bufferSize: 1024,
365421
});
366422

367-
for await (const entry of dir) {
368-
if (entries.length >= maxEntries) break;
369-
370-
const relFromTarget = path.relative(targetPath, entry.parentPath);
371-
const depth =
372-
relFromTarget === "" ? 0 : relFromTarget.split(path.sep).length;
373-
374-
if (depth > maxDepth) continue;
375-
376-
// Skip entries nested inside hidden dirs or node_modules,
377-
// but still list the skip-dirs themselves at their parent level.
378-
if (relFromTarget !== "") {
379-
const segments = relFromTarget.split(path.sep);
380-
if (segments.some((s) => s.startsWith(".") || SKIP_DIRS.has(s))) {
381-
continue;
382-
}
423+
for await (const dirent of dir) {
424+
if (entries.length >= maxEntries) {
425+
break;
383426
}
384-
385-
const relPath = path.relative(cwd, path.join(entry.parentPath, entry.name));
386-
387-
// If this entry is a symlink, verify it doesn't escape the project directory.
388-
if (entry.isSymbolicLink()) {
389-
try {
390-
safePath(cwd, relPath);
391-
} catch {
392-
continue;
393-
}
427+
const parsed = toDirEntry(dirent, cwd, targetPath, maxDepth);
428+
if (parsed) {
429+
entries.push(parsed);
394430
}
395-
396-
const type = entry.isDirectory() ? "directory" : "file";
397-
entries.push({ name: entry.name, path: relPath, type });
398431
}
399432
} catch {
400433
// Directory doesn't exist or can't be read

0 commit comments

Comments
 (0)