/** * File system helpers — file loading and glob discovery. * * Composable primitives for `analyzeFromFiles` / `discoverSourceFiles`. For * most use cases prefer those higher-level entry points. * * Build tools that want to drive resolve outside the session can construct an * `ImportResolver` directly and pass it to `createAnalysisSession`. * * @see `analyzeFromFiles` for the high-level disk-discovery wrapper * @see `discoverSourceFiles` for exports-first discovery with glob fallback * * @module */ import {readFile} from 'node:fs/promises'; import {resolve, isAbsolute} from 'node:path'; import {glob} from 'tinyglobby'; import type {SourceFileInfo} from './source.js'; import {toPosixPath} from './paths.js'; import {MAX_FILE_CONCURRENCY, map_concurrent} from './concurrency.js'; /** * Load a single source file from disk. * * Accepts either relative or absolute paths. Relative paths are resolved against `projectRoot`. * * @param path - file path (relative to `projectRoot` or absolute) * @param projectRoot - absolute path to project root * @returns source file info with content loaded * @throws Error if the file cannot be read (e.g., missing or permission denied) * * @example * ```ts * const file = await loadFile('src/lib/math.ts', process.cwd()); * // {id: '/abs/path/to/src/lib/math.ts', content: '...'} * ``` */ export const loadFile = async (path: string, projectRoot: string): Promise<SourceFileInfo> => { const absolutePath = isAbsolute(path) ? path : resolve(projectRoot, path); const content = await readFile(absolutePath, 'utf-8'); return { id: toPosixPath(absolutePath), content, }; }; /** * Glob extension list for source files. Matches the extension set covered by * `getDefaultAnalyzer` in `source.ts` — keep in sync if a new analyzable * extension is added there. */ const SOURCE_FILE_EXTENSIONS = 'ts,js,svelte,css,json'; /** * Build an include pattern array from source paths. * * Each path becomes a `<path>/**\/*.{ts,js,svelte,css,json}` glob. Used by * `discoverSourceFiles` to derive a default `include` from * `sourceOptions.sourcePaths` when no explicit pattern is supplied — keeps * the glob fallback consistent with custom `sourcePaths` instead of silently * defaulting to `src/lib`. * * @example * ```ts * deriveIncludePatterns(['packages/foo', 'packages/bar']) * // => ['packages/foo/**\/*.{ts,js,svelte,css,json}', 'packages/bar/**\/*.{ts,js,svelte,css,json}'] * ``` */ export const deriveIncludePatterns = (sourcePaths: ReadonlyArray<string>): Array<string> => sourcePaths.map((p) => `${p}/**/*.{${SOURCE_FILE_EXTENSIONS}}`); /** * Options for `globFiles`. */ export interface GlobFilesOptions { /** Absolute path to project root. */ projectRoot: string; /** Glob patterns to include (relative to `projectRoot`). */ include: Array<string>; /** Optional glob patterns to exclude. */ exclude?: Array<string>; } /** * Discover source files via glob patterns. * * @param options - glob configuration * @returns array of source files with content loaded * @throws Error if any matched file cannot be read — `Promise.all` rejects on the first read failure * * @example * ```ts * const files = await globFiles({ * projectRoot: process.cwd(), * include: deriveIncludePatterns(['src/lib']), * exclude: DEFAULT_SOURCE_OPTIONS.exclude, * }); * ``` */ export const globFiles = async (options: GlobFilesOptions): Promise<Array<SourceFileInfo>> => { const {projectRoot, include, exclude} = options; const filePaths = await glob(include, { cwd: projectRoot, ignore: exclude, absolute: true, }); // Bounded concurrency to keep FD pressure under the typical ulimit on // large projects. See `concurrency.ts`. return map_concurrent(filePaths, MAX_FILE_CONCURRENCY, async (id) => { const content = await readFile(id, 'utf-8'); return {id: toPosixPath(id), content}; }); };
{ "path": "files.ts", "declarations": [ { "name": "loadFile", "kind": "function", "docComment": "Load a single source file from disk.\n\nAccepts either relative or absolute paths. Relative paths are resolved against `projectRoot`.", "typeSignature": "(path: string, projectRoot: string): Promise<SourceFileInfo>", "sourceLine": 40, "examples": [ "```ts\nconst file = await loadFile('src/lib/math.ts', process.cwd());\n// {id: '/abs/path/to/src/lib/math.ts', content: '...'}\n```" ], "throws": [ { "type": "Error", "description": "if the file cannot be read (e.g., missing or permission denied)" } ], "parameters": [ { "name": "path", "type": "string", "description": "file path (relative to `projectRoot` or absolute)" }, { "name": "projectRoot", "type": "string", "description": "absolute path to project root" } ], "returnType": "Promise<SourceFileInfo>", "returnDescription": "source file info with content loaded" }, { "name": "deriveIncludePatterns", "kind": "function", "docComment": "Build an include pattern array from source paths.\n\nEach path becomes a `<path>/**\\/*.{ts,js,svelte,css,json}` glob. Used by\n`discoverSourceFiles` to derive a default `include` from\n`sourceOptions.sourcePaths` when no explicit pattern is supplied — keeps\nthe glob fallback consistent with custom `sourcePaths` instead of silently\ndefaulting to `src/lib`.", "typeSignature": "(sourcePaths: readonly string[]): string[]", "sourceLine": 72, "examples": [ "```ts\nderiveIncludePatterns(['packages/foo', 'packages/bar'])\n// => ['packages/foo/**\\/*.{ts,js,svelte,css,json}', 'packages/bar/**\\/*.{ts,js,svelte,css,json}']\n```" ], "alsoExportedFrom": [ "index.ts" ], "parameters": [ { "name": "sourcePaths", "type": "readonly string[]" } ], "returnType": "string[]" }, { "name": "GlobFilesOptions", "kind": "interface", "docComment": "Options for `globFiles`.", "typeSignature": "GlobFilesOptions", "sourceLine": 78, "members": [ { "name": "projectRoot", "kind": "variable", "docComment": "Absolute path to project root.", "typeSignature": "string" }, { "name": "include", "kind": "variable", "docComment": "Glob patterns to include (relative to `projectRoot`).", "typeSignature": "Array<string>" }, { "name": "exclude", "kind": "variable", "docComment": "Optional glob patterns to exclude.", "typeSignature": "Array<string>", "optional": true } ] }, { "name": "globFiles", "kind": "function", "docComment": "Discover source files via glob patterns.", "typeSignature": "(options: GlobFilesOptions): Promise<SourceFileInfo[]>", "sourceLine": 103, "examples": [ "```ts\nconst files = await globFiles({\n projectRoot: process.cwd(),\n include: deriveIncludePatterns(['src/lib']),\n exclude: DEFAULT_SOURCE_OPTIONS.exclude,\n});\n```" ], "throws": [ { "type": "Error", "description": "if any matched file cannot be read — `Promise.all` rejects on the first read failure" } ], "parameters": [ { "name": "options", "type": "GlobFilesOptions", "description": "glob configuration" } ], "returnType": "Promise<SourceFileInfo[]>", "returnDescription": "array of source files with content loaded" } ], "moduleComment": "File system helpers — file loading and glob discovery.\n\nComposable primitives for `analyzeFromFiles` / `discoverSourceFiles`. For\nmost use cases prefer those higher-level entry points.\n\nBuild tools that want to drive resolve outside the session can construct an\n`ImportResolver` directly and pass it to `createAnalysisSession`.\n\n@see `analyzeFromFiles` for the high-level disk-discovery wrapper\n@see `discoverSourceFiles` for exports-first discovery with glob fallback", "dependencies": [ "concurrency.ts", "paths.ts", "source.ts" ], "dependents": [ "discovery.ts", "index.ts" ] }