/** * Source configuration, path extraction, and file filtering. * * Provides `ModuleSourceOptions` configuration management and functions * that operate on source files using those options: path extraction, * source detection, dependency filtering, and file collection. * * @see `source.ts` for pure file type predicates (`isTypescript`, `isSvelte`, etc.) * @see `analyze.ts` for consumers (`analyze`, `analyzeFromFiles`) * * @module */ import {resolve, relative} from 'node:path'; import picomatch from 'picomatch'; import {type AnalyzerType, type SourceFileInfo, getDefaultAnalyzer} from './source.js'; import {toPosixPath} from './paths.js'; import {compareStrings} from './postprocess.js'; /** * Configuration for module source detection and path extraction. * * Uses proper path semantics with `projectRoot` as the base for all path operations. * Paths are matched using `startsWith` rather than substring search, which correctly * handles nested directories without special heuristics. * * @example * ```ts * const options = createSourceOptions(process.cwd(), { * sourcePaths: ['src/lib', 'src/routes'], * sourceRoot: 'src', * }); * ``` */ export interface ModuleSourceOptions { /** * Path to the project root directory. * * All `sourcePaths` are relative to this. Typically `process.cwd()`. * Normalized (resolved to absolute, posixified to forward slashes, * trailing slash stripped) by `normalizeSourceOptions`, which returns a * new options object. Internal callers (`isSource`, `extractPath`) assume * this is already POSIX form — construct via `createSourceOptions` / * `normalizeSourceOptions` rather than building `ModuleSourceOptions` * literals by hand on Windows. * * @example * ```ts * '/home/user/my-project' * ``` */ projectRoot: string; /** * Source directory paths to include, relative to `projectRoot`. * * Normalized (leading/trailing slashes stripped) by `normalizeSourceOptions`, * which returns a new options object. * * @example * ```ts * ['src/lib'] // single source directory * ``` * @example * ```ts * ['src/lib', 'src/routes'] // multiple directories * ``` */ sourcePaths: Array<string>; /** * Source root for extracting relative module paths, relative to `projectRoot`. * * Normalized (leading/trailing slashes stripped; `'.'` collapsed to `''`) * by `normalizeSourceOptions`, which returns a new options object. * * When omitted: * - Single `sourcePath`: defaults to that path * - Multiple `sourcePaths`: auto-derived as the longest common directory prefix * (or `''` when paths share no common prefix — produces project-relative module paths) * * @example * ```ts * 'src/lib' // module paths like 'foo.ts', 'utils/bar.ts' * ``` * @example * ```ts * 'src' // module paths like 'lib/foo.ts', 'routes/page.svelte' * ``` * @example * ```ts * '' // or '.': module paths stay project-relative, e.g. 'src/lib/foo.ts' * ``` */ sourceRoot?: string; /** * Glob patterns to exclude from analysis, relative to `projectRoot`. * * Applied at both stages of the pipeline: * - **Discovery time** by `globFiles`/`discoverFromExports`, preventing matched files from being loaded. * - **Analysis time** by `isSource()` against `relative(projectRoot, absolutePath)`, * catching files that enter through TypeScript import resolution * (e.g., a source file imports a test helper). * * `analyzeFromFiles` accepts a top-level `exclude` shortcut that merges into this field. * * Compiled to a matcher once per options object via picomatch and cached by reference; * mutating this array post-`isSource`-call has no effect — pass through * `normalizeSourceOptions` (which returns a fresh object) or otherwise build a * new options object to apply changes. * * @default `['**\/*.test.ts', '**\/*.spec.ts']` */ exclude: Array<string>; /** * Determine which analyzer to use for a file path. * * Called for files in source directories. Return an `AnalyzerType` or `null` to skip: * - `'typescript'` — TypeScript/JS files analyzed via TypeScript compiler API * - `'svelte'` — Svelte components analyzed via svelte2tsx + TypeScript compiler API * - `'css'` — CSS files included as modules with no declarations * - `'json'` — JSON files included as modules with no declarations * - `null` — skip the file * * @default Uses file extension: `.svelte` → svelte, `.ts`/`.js` → typescript, `.css` → css, `.json` → json * * @example * ```ts * // Add MDsveX support * getAnalyzerType: (path) => { * if (path.endsWith('.svelte') || path.endsWith('.svx')) return 'svelte'; * if (path.endsWith('.ts') || path.endsWith('.js')) return 'typescript'; * if (path.endsWith('.css')) return 'css'; * if (path.endsWith('.json')) return 'json'; * return null; * } * ``` * * @example * ```ts * // Include .d.ts files * getAnalyzerType: (path) => { * if (path.endsWith('.svelte')) return 'svelte'; * if (path.endsWith('.ts') || path.endsWith('.d.ts') || path.endsWith('.js')) return 'typescript'; * if (path.endsWith('.css')) return 'css'; * if (path.endsWith('.json')) return 'json'; * return null; * } * ``` */ getAnalyzerType: (path: string) => AnalyzerType | null; } /** * Default source options preset (without `projectRoot`). * * Use with `createSourceOptions` to build complete options. * Contains all `ModuleSourceOptions` fields except `projectRoot`, * which is provided separately as the first argument to `createSourceOptions`. */ export type SourceOptionsDefaults = Omit<ModuleSourceOptions, 'projectRoot'>; /** * Default partial options for standard SvelteKit library structure. * * Does not include `projectRoot` — use `createSourceOptions` to create * complete options with your project root. * * `exclude` is the single source of truth for filtering: globs applied at both * discovery time (by `globFiles`/`discoverFromExports`) and analysis time * (by `isSource()` against project-root-relative paths). * * @see `createSourceOptions` for the typical way to build complete options */ export const DEFAULT_SOURCE_OPTIONS: SourceOptionsDefaults = { sourcePaths: ['src/lib'], exclude: ['**/*.test.ts', '**/*.spec.ts'], getAnalyzerType: getDefaultAnalyzer, }; /** * Create complete, normalized, validated source options from project root and optional overrides. * * Merges `overrides` with `DEFAULT_SOURCE_OPTIONS`, then normalizes via * `normalizeSourceOptions` — so the returned object always has an absolute * `projectRoot`, slash-stripped path entries, and an explicit `sourceRoot` * (auto-derived for multi-path layouts). Throws on validation failure. * * @param projectRoot - path to project root (typically `process.cwd()`); resolved to absolute * @param overrides - optional overrides for default options * @throws Error if validation fails (empty `sourcePaths`, or `sourceRoot` not a prefix of all `sourcePaths`) * * @example * ```ts * // Standard SvelteKit library * const options = createSourceOptions(process.cwd()); * ``` * * @example * ```ts * // Multiple source directories * const options = createSourceOptions(process.cwd(), { * sourcePaths: ['src/lib', 'src/routes'], * sourceRoot: 'src', * }); * ``` * * @example * ```ts * // Custom exclusions * const options = createSourceOptions(process.cwd(), { * exclude: ['**\/*.test.ts', '**\/*.internal.ts'], * }); * ``` */ export const createSourceOptions = ( projectRoot: string, overrides?: Partial<SourceOptionsDefaults>, ): ModuleSourceOptions => normalizeSourceOptions({ projectRoot, ...DEFAULT_SOURCE_OPTIONS, ...overrides, }); /** * Normalize and validate `ModuleSourceOptions`, returning a new options object. * * Normalization: * - `projectRoot` resolved to absolute via `path.resolve` (relative paths resolve against cwd) * - Trailing slash stripped from `projectRoot` * - Leading/trailing slashes stripped from `sourcePaths` entries * - Leading/trailing slashes stripped from `sourceRoot` (if provided) * - `sourceRoot` of `'.'` is normalized to `''` (project root sentinel) * * Validation (after normalization): * 1. `sourcePaths` has at least one entry * 2. `sourceRoot` (if provided and non-empty) is a prefix of all `sourcePaths` * * When `sourceRoot` is omitted and multiple `sourcePaths` are provided, * `sourceRoot` is auto-derived as their longest common path prefix. If the * paths share no common prefix, the derived root is `''` and `extractPath` * produces project-relative module paths. * * Returns a fresh object — the input is not mutated. Re-normalization produces * a fresh identity, which naturally invalidates the `excludeMatcherCache` * (keyed by options-object identity) without a separate "rebuild a fresh * object" rule. * * @returns a new `ModuleSourceOptions` with normalized fields * @throws Error if validation fails * * @example * ```ts * // Normalization: slashes stripped, relative projectRoot resolved * const normalized = normalizeSourceOptions({projectRoot: '.', sourcePaths: ['/src/lib/'], ...}); * // normalized.projectRoot is now absolute, normalized.sourcePaths is ['src/lib'] * ``` */ export const normalizeSourceOptions = (options: ModuleSourceOptions): ModuleSourceOptions => { // Normalize projectRoot: resolve to absolute, posixify, strip trailing slash. // Posixify after `resolve` because `path.resolve` returns native separators // on Windows; everything internal compares against forward-slash form. let projectRoot = toPosixPath(resolve(options.projectRoot)); if (projectRoot.length > 1 && projectRoot.endsWith('/')) { projectRoot = projectRoot.slice(0, -1); } // Normalize sourcePaths: posixify (in case a Windows user supplies // `'src\\lib'`), then strip leading/trailing slashes. const sourcePaths = options.sourcePaths.map((p) => stripSlashes(toPosixPath(p))); // Validate sourcePaths non-empty if (sourcePaths.length === 0) { throw new Error( 'ModuleSourceOptions.sourcePaths must have at least one entry. ' + "For SvelteKit projects, the default is ['src/lib']; for plain TS, try ['src'].", ); } // Normalize sourceRoot: posixify, strip leading/trailing slashes. // `.` is treated as the project root sentinel — equivalent to an empty string, // which `extractPath` interprets as "strip projectRoot only" (module paths // stay project-relative). `.` is the ergonomic CLI form; `''` is the // shell-quoting form. Both reach the same effective state. let sourceRoot = options.sourceRoot === undefined ? undefined : stripSlashes(toPosixPath(options.sourceRoot)); if (sourceRoot === '.') sourceRoot = ''; // Auto-derive sourceRoot for multiple sourcePaths when not explicitly provided. // Empty common prefix is a valid result — `extractPath` will produce // project-relative module paths (e.g., `lib/foo.ts`, `routes/page.svelte`) // instead of stripping a common directory prefix. if (sourceRoot === undefined && sourcePaths.length > 1) { sourceRoot = deriveCommonPrefix(sourcePaths); } // Validate sourceRoot prefix constraint (if sourceRoot is set — either explicit or auto-derived) if (sourceRoot !== undefined && sourceRoot !== '') { for (const sourcePath of sourcePaths) { if (sourcePath !== sourceRoot && !sourcePath.startsWith(sourceRoot + '/')) { throw new Error( `sourcePaths entry "${sourcePath}" must start with sourceRoot "${sourceRoot}". ` + `extractPath uses sourceRoot to compute module paths.`, ); } } } return {...options, projectRoot, sourcePaths, sourceRoot}; }; /** Strip all leading and trailing forward slashes from a path segment. */ const stripSlashes = (p: string): string => { let result = p; while (result.startsWith('/')) result = result.slice(1); while (result.endsWith('/')) result = result.slice(0, -1); return result; }; /** * Derive the longest common directory prefix from an array of paths. * * @returns common prefix (e.g., `['src/lib', 'src/routes']` → `'src'`), * or empty string if paths share no common prefix */ const deriveCommonPrefix = (paths: Array<string>): string => { if (paths.length === 0) return ''; const segments = paths[0]!.split('/'); let common = ''; for (const segment of segments) { const candidate = common ? common + '/' + segment : segment; if (paths.every((p) => p === candidate || p.startsWith(candidate + '/'))) { common = candidate; } else { break; } } return common; }; /** * Get the effective `sourceRoot` from options. * * Returns `sourceRoot` if provided, otherwise: * - Single `sourcePath`: returns that path * - Multiple `sourcePaths`: derives the longest common directory prefix * * @returns the effective source root path */ export const getSourceRoot = (options: ModuleSourceOptions): string => { if (options.sourceRoot !== undefined) { return options.sourceRoot; } if (options.sourcePaths.length === 1) { return options.sourcePaths[0]!; } return deriveCommonPrefix(options.sourcePaths); }; /** * Extract module path relative to source root from absolute source ID. * * Uses proper path semantics: strips `projectRoot/sourceRoot/` prefix. * * @param sourceId - absolute path to the source file * @param options - module source options for path extraction * * @example * ```ts * const options = createSourceOptions('/home/user/project'); * extractPath('/home/user/project/src/lib/foo.ts', options) // => 'foo.ts' * extractPath('/home/user/project/src/lib/nested/bar.svelte', options) // => 'nested/bar.svelte' * ``` * * @example * ```ts * const options = createSourceOptions('/home/user/project', { * sourcePaths: ['src/lib', 'src/routes'], * sourceRoot: 'src', * }); * extractPath('/home/user/project/src/lib/foo.ts', options) // => 'lib/foo.ts' * extractPath('/home/user/project/src/routes/page.svelte', options) // => 'routes/page.svelte' * ``` */ export const extractPath = (sourceId: string, options: ModuleSourceOptions): string => { // Posixify input so the prefix slice works regardless of which separator // the caller's path system uses. `options.projectRoot` is already POSIX. const posixId = toPosixPath(sourceId); const effectiveRoot = getSourceRoot(options); // Build the full prefix: projectRoot + '/' + sourceRoot + '/' // When sourceRoot is empty, prefix is just projectRoot + '/' const prefix = effectiveRoot ? options.projectRoot + '/' + effectiveRoot + '/' : options.projectRoot + '/'; if (posixId.startsWith(prefix)) { return posixId.slice(prefix.length); } // Fallback: return full path if prefix doesn't match (shouldn't happen with valid inputs) return posixId; }; /** * Compiled glob-matcher cache for `options.exclude`, keyed by options object identity. * * Picomatch compilation is non-trivial; caching avoids re-parsing the same patterns * on every `isSource()` call. Mutating `options.exclude` post-cache-population has * no effect — callers that need to change the exclude list must build a fresh * options object (different identity). `normalizeSourceOptions` returns a new * object, so re-running it through normalize naturally produces a fresh identity * and the cache populates fresh on first access. */ const excludeMatcherCache: WeakMap<ModuleSourceOptions, (relPath: string) => boolean> = new WeakMap(); const getExcludeMatcher = (options: ModuleSourceOptions): ((relPath: string) => boolean) => { let matcher = excludeMatcherCache.get(options); if (!matcher) { matcher = options.exclude.length === 0 ? () => false : picomatch(options.exclude); excludeMatcherCache.set(options, matcher); } return matcher; }; /** * Check if a path is an analyzable source file. * * Combines all filtering: source directory paths, exclude globs, and analyzer * availability. This is the single check for whether a file should be * included in library analysis. * * Uses proper path semantics with `startsWith` matching against * `projectRoot/sourcePath/`. No heuristics needed — nested directories are * correctly excluded by the prefix check. * * Order is sourceDir-then-exclude: the prefix check guarantees the path lives * under `projectRoot` before relativization, so `relative()` always produces * a clean glob-shaped string for the matcher. Files outside `projectRoot` * (rare; would require monorepo path mapping) short-circuit at the prefix * check before reaching the matcher. * * @param path - full absolute path to check * @param options - module source options for filtering * @returns true if the path is an analyzable source file * * @example * ```ts * const options = createSourceOptions('/home/user/project'); * isSource('/home/user/project/src/lib/foo.ts', options) // => true * isSource('/home/user/project/src/lib/styles.css', options) // => true * isSource('/home/user/project/src/lib/data.json', options) // => true * isSource('/home/user/project/src/lib/foo.test.ts', options) // => false (excluded) * isSource('/home/user/project/src/fixtures/mini/src/lib/bar.ts', options) // => false (wrong prefix) * ``` */ export const isSource = (path: string, options: ModuleSourceOptions): boolean => { // Posixify input — callers may pass native paths (Windows backslashes). // `options.projectRoot` and `sourcePaths` are already POSIX after // `normalizeSourceOptions`. const posixPath = toPosixPath(path); // Source-dir prefix check first — guarantees the path lives under projectRoot // before we relativize for glob matching. Out-of-root files (which can't be // sources anyway) short-circuit here without reaching the matcher. const inSourceDir = options.sourcePaths.some((sourcePath) => { const fullPrefix = options.projectRoot + '/' + sourcePath + '/'; return posixPath.startsWith(fullPrefix); }); if (!inSourceDir) return false; // Glob-match the project-root-relative path against `exclude`. `relative` // returns native separators on Windows; posixify so the matcher sees the // same forward-slash form the user wrote their globs in. const relPath = toPosixPath(relative(options.projectRoot, posixPath)); if (getExcludeMatcher(options)(relPath)) return false; // Check if file type is analyzable. Pass the posixified form so analyzer // callbacks see the canonical id, not a native-separator variant. return options.getAnalyzerType(posixPath) !== null; }; /** * Extract dependencies and dependents for a module from source file info. * * Filters to only include source modules (excludes external packages, node_modules, tests). * Returns sorted arrays of module paths (relative to `sourceRoot`) for deterministic output. * * Native paths in `sourceFile.dependencies`/`dependents` are accepted — * `isSource` and `extractPath` posixify their inputs, so direct callers with * hand-built input need not pre-normalize. * * Accepts `SourceFileInfo` plus an optional `dependents` field — the public * input type carries only `dependencies` (caller-supplied opt-in), while * `dependents` is computed downstream by `computeDependents` and flows through * as an enriched shape. * * @param sourceFile - the source file info to extract dependencies from * @param options - module source options for filtering and path extraction * @returns sorted arrays of module paths (relative to `sourceRoot`) for dependencies and dependents */ export const extractDependencies = ( sourceFile: SourceFileInfo & {dependents?: ReadonlyArray<string>}, options: ModuleSourceOptions, ): {dependencies: Array<string>; dependents: Array<string>} => { const dependencies: Array<string> = []; const dependents: Array<string> = []; // Extract dependencies (files this module imports) if provided if (sourceFile.dependencies) { for (const depId of sourceFile.dependencies) { if (isSource(depId, options)) { dependencies.push(extractPath(depId, options)); } } } // Extract dependents (files that import this module) if provided if (sourceFile.dependents) { for (const dependentId of sourceFile.dependents) { if (isSource(dependentId, options)) { dependents.push(extractPath(dependentId, options)); } } } // Sort for deterministic output dependencies.sort(compareStrings); dependents.sort(compareStrings); return {dependencies, dependents}; };
{ "path": "source-config.ts", "declarations": [ { "name": "ModuleSourceOptions", "kind": "interface", "docComment": "Configuration for module source detection and path extraction.\n\nUses proper path semantics with `projectRoot` as the base for all path operations.\nPaths are matched using `startsWith` rather than substring search, which correctly\nhandles nested directories without special heuristics.", "typeSignature": "ModuleSourceOptions", "sourceLine": 36, "examples": [ "```ts\nconst options = createSourceOptions(process.cwd(), {\n sourcePaths: ['src/lib', 'src/routes'],\n sourceRoot: 'src',\n});\n```" ], "alsoExportedFrom": [ "index.ts" ], "members": [ { "name": "projectRoot", "kind": "variable", "docComment": "Path to the project root directory.\n\nAll `sourcePaths` are relative to this. Typically `process.cwd()`.\nNormalized (resolved to absolute, posixified to forward slashes,\ntrailing slash stripped) by `normalizeSourceOptions`, which returns a\nnew options object. Internal callers (`isSource`, `extractPath`) assume\nthis is already POSIX form — construct via `createSourceOptions` /\n`normalizeSourceOptions` rather than building `ModuleSourceOptions`\nliterals by hand on Windows.", "typeSignature": "string", "examples": [ "```ts\n'/home/user/my-project'\n```" ] }, { "name": "sourcePaths", "kind": "variable", "docComment": "Source directory paths to include, relative to `projectRoot`.\n\nNormalized (leading/trailing slashes stripped) by `normalizeSourceOptions`,\nwhich returns a new options object.", "typeSignature": "Array<string>", "examples": [ "```ts\n['src/lib'] // single source directory\n```", "```ts\n['src/lib', 'src/routes'] // multiple directories\n```" ] }, { "name": "sourceRoot", "kind": "variable", "docComment": "Source root for extracting relative module paths, relative to `projectRoot`.\n\nNormalized (leading/trailing slashes stripped; `'.'` collapsed to `''`)\nby `normalizeSourceOptions`, which returns a new options object.\n\nWhen omitted:\n- Single `sourcePath`: defaults to that path\n- Multiple `sourcePaths`: auto-derived as the longest common directory prefix\n (or `''` when paths share no common prefix — produces project-relative module paths)", "typeSignature": "string", "examples": [ "```ts\n'src/lib' // module paths like 'foo.ts', 'utils/bar.ts'\n```", "```ts\n'src' // module paths like 'lib/foo.ts', 'routes/page.svelte'\n```", "```ts\n'' // or '.': module paths stay project-relative, e.g. 'src/lib/foo.ts'\n```" ], "optional": true }, { "name": "exclude", "kind": "variable", "docComment": "Glob patterns to exclude from analysis, relative to `projectRoot`.\n\nApplied at both stages of the pipeline:\n- **Discovery time** by `globFiles`/`discoverFromExports`, preventing matched files from being loaded.\n- **Analysis time** by `isSource()` against `relative(projectRoot, absolutePath)`,\n catching files that enter through TypeScript import resolution\n (e.g., a source file imports a test helper).\n\n`analyzeFromFiles` accepts a top-level `exclude` shortcut that merges into this field.\n\nCompiled to a matcher once per options object via picomatch and cached by reference;\nmutating this array post-`isSource`-call has no effect — pass through\n`normalizeSourceOptions` (which returns a fresh object) or otherwise build a\nnew options object to apply changes.", "typeSignature": "Array<string>", "defaultValue": "`['**\\/*.test.ts', '**\\/*.spec.ts']`" }, { "name": "getAnalyzerType", "kind": "variable", "docComment": "Determine which analyzer to use for a file path.\n\nCalled for files in source directories. Return an `AnalyzerType` or `null` to skip:\n- `'typescript'` — TypeScript/JS files analyzed via TypeScript compiler API\n- `'svelte'` — Svelte components analyzed via svelte2tsx + TypeScript compiler API\n- `'css'` — CSS files included as modules with no declarations\n- `'json'` — JSON files included as modules with no declarations\n- `null` — skip the file", "typeSignature": "(path: string) => AnalyzerType | null", "examples": [ "```ts\n// Add MDsveX support\ngetAnalyzerType: (path) => {\n if (path.endsWith('.svelte') || path.endsWith('.svx')) return 'svelte';\n if (path.endsWith('.ts') || path.endsWith('.js')) return 'typescript';\n if (path.endsWith('.css')) return 'css';\n if (path.endsWith('.json')) return 'json';\n return null;\n}\n```", "```ts\n// Include .d.ts files\ngetAnalyzerType: (path) => {\n if (path.endsWith('.svelte')) return 'svelte';\n if (path.endsWith('.ts') || path.endsWith('.d.ts') || path.endsWith('.js')) return 'typescript';\n if (path.endsWith('.css')) return 'css';\n if (path.endsWith('.json')) return 'json';\n return null;\n}\n```" ], "defaultValue": "Uses file extension: `.svelte` → svelte, `.ts`/`.js` → typescript, `.css` → css, `.json` → json" } ] }, { "name": "SourceOptionsDefaults", "kind": "type", "docComment": "Default source options preset (without `projectRoot`).\n\nUse with `createSourceOptions` to build complete options.\nContains all `ModuleSourceOptions` fields except `projectRoot`,\nwhich is provided separately as the first argument to `createSourceOptions`.", "typeSignature": "SourceOptionsDefaults", "sourceLine": 160, "alsoExportedFrom": [ "index.ts" ], "members": [ { "name": "sourcePaths", "kind": "variable", "docComment": "Source directory paths to include, relative to `projectRoot`.\n\nNormalized (leading/trailing slashes stripped) by `normalizeSourceOptions`,\nwhich returns a new options object.", "typeSignature": "string[]", "examples": [ "```ts\n['src/lib'] // single source directory\n```", "```ts\n['src/lib', 'src/routes'] // multiple directories\n```" ] }, { "name": "sourceRoot", "kind": "variable", "docComment": "Source root for extracting relative module paths, relative to `projectRoot`.\n\nNormalized (leading/trailing slashes stripped; `'.'` collapsed to `''`)\nby `normalizeSourceOptions`, which returns a new options object.\n\nWhen omitted:\n- Single `sourcePath`: defaults to that path\n- Multiple `sourcePaths`: auto-derived as the longest common directory prefix\n (or `''` when paths share no common prefix — produces project-relative module paths)", "typeSignature": "string", "examples": [ "```ts\n'src/lib' // module paths like 'foo.ts', 'utils/bar.ts'\n```", "```ts\n'src' // module paths like 'lib/foo.ts', 'routes/page.svelte'\n```", "```ts\n'' // or '.': module paths stay project-relative, e.g. 'src/lib/foo.ts'\n```" ], "optional": true }, { "name": "exclude", "kind": "variable", "docComment": "Glob patterns to exclude from analysis, relative to `projectRoot`.\n\nApplied at both stages of the pipeline:\n- **Discovery time** by `globFiles`/`discoverFromExports`, preventing matched files from being loaded.\n- **Analysis time** by `isSource()` against `relative(projectRoot, absolutePath)`,\n catching files that enter through TypeScript import resolution\n (e.g., a source file imports a test helper).\n\n`analyzeFromFiles` accepts a top-level `exclude` shortcut that merges into this field.\n\nCompiled to a matcher once per options object via picomatch and cached by reference;\nmutating this array post-`isSource`-call has no effect — pass through\n`normalizeSourceOptions` (which returns a fresh object) or otherwise build a\nnew options object to apply changes.", "typeSignature": "string[]", "defaultValue": "`['**\\/*.test.ts', '**\\/*.spec.ts']`" }, { "name": "getAnalyzerType", "kind": "function", "docComment": "Determine which analyzer to use for a file path.\n\nCalled for files in source directories. Return an `AnalyzerType` or `null` to skip:\n- `'typescript'` — TypeScript/JS files analyzed via TypeScript compiler API\n- `'svelte'` — Svelte components analyzed via svelte2tsx + TypeScript compiler API\n- `'css'` — CSS files included as modules with no declarations\n- `'json'` — JSON files included as modules with no declarations\n- `null` — skip the file", "typeSignature": "(path: string): AnalyzerType | null", "examples": [ "```ts\n// Add MDsveX support\ngetAnalyzerType: (path) => {\n if (path.endsWith('.svelte') || path.endsWith('.svx')) return 'svelte';\n if (path.endsWith('.ts') || path.endsWith('.js')) return 'typescript';\n if (path.endsWith('.css')) return 'css';\n if (path.endsWith('.json')) return 'json';\n return null;\n}\n```", "```ts\n// Include .d.ts files\ngetAnalyzerType: (path) => {\n if (path.endsWith('.svelte')) return 'svelte';\n if (path.endsWith('.ts') || path.endsWith('.d.ts') || path.endsWith('.js')) return 'typescript';\n if (path.endsWith('.css')) return 'css';\n if (path.endsWith('.json')) return 'json';\n return null;\n}\n```" ], "parameters": [ { "name": "path", "type": "string" } ], "returnType": "AnalyzerType | null" } ] }, { "name": "DEFAULT_SOURCE_OPTIONS", "kind": "variable", "docComment": "Default partial options for standard SvelteKit library structure.\n\nDoes not include `projectRoot` — use `createSourceOptions` to create\ncomplete options with your project root.\n\n`exclude` is the single source of truth for filtering: globs applied at both\ndiscovery time (by `globFiles`/`discoverFromExports`) and analysis time\n(by `isSource()` against project-root-relative paths).", "typeSignature": "SourceOptionsDefaults", "sourceLine": 174, "seeAlso": [ "`createSourceOptions` for the typical way to build complete options" ], "alsoExportedFrom": [ "index.ts" ] }, { "name": "createSourceOptions", "kind": "function", "docComment": "Create complete, normalized, validated source options from project root and optional overrides.\n\nMerges `overrides` with `DEFAULT_SOURCE_OPTIONS`, then normalizes via\n`normalizeSourceOptions` — so the returned object always has an absolute\n`projectRoot`, slash-stripped path entries, and an explicit `sourceRoot`\n(auto-derived for multi-path layouts). Throws on validation failure.", "typeSignature": "(projectRoot: string, overrides?: Partial<SourceOptionsDefaults> | undefined): ModuleSourceOptions", "sourceLine": 215, "examples": [ "```ts\n// Standard SvelteKit library\nconst options = createSourceOptions(process.cwd());\n```", "```ts\n// Multiple source directories\nconst options = createSourceOptions(process.cwd(), {\n sourcePaths: ['src/lib', 'src/routes'],\n sourceRoot: 'src',\n});\n```", "```ts\n// Custom exclusions\nconst options = createSourceOptions(process.cwd(), {\n exclude: ['**\\/*.test.ts', '**\\/*.internal.ts'],\n});\n```" ], "throws": [ { "type": "Error", "description": "if validation fails (empty `sourcePaths`, or `sourceRoot` not a prefix of all `sourcePaths`)" } ], "alsoExportedFrom": [ "index.ts" ], "parameters": [ { "name": "projectRoot", "type": "string", "description": "path to project root (typically `process.cwd()`); resolved to absolute" }, { "name": "overrides", "type": "Partial<SourceOptionsDefaults> | undefined", "optional": true, "description": "optional overrides for default options" } ], "returnType": "ModuleSourceOptions" }, { "name": "normalizeSourceOptions", "kind": "function", "docComment": "Normalize and validate `ModuleSourceOptions`, returning a new options object.\n\nNormalization:\n- `projectRoot` resolved to absolute via `path.resolve` (relative paths resolve against cwd)\n- Trailing slash stripped from `projectRoot`\n- Leading/trailing slashes stripped from `sourcePaths` entries\n- Leading/trailing slashes stripped from `sourceRoot` (if provided)\n- `sourceRoot` of `'.'` is normalized to `''` (project root sentinel)\n\nValidation (after normalization):\n1. `sourcePaths` has at least one entry\n2. `sourceRoot` (if provided and non-empty) is a prefix of all `sourcePaths`\n\nWhen `sourceRoot` is omitted and multiple `sourcePaths` are provided,\n`sourceRoot` is auto-derived as their longest common path prefix. If the\npaths share no common prefix, the derived root is `''` and `extractPath`\nproduces project-relative module paths.\n\nReturns a fresh object — the input is not mutated. Re-normalization produces\na fresh identity, which naturally invalidates the `excludeMatcherCache`\n(keyed by options-object identity) without a separate \"rebuild a fresh\nobject\" rule.", "typeSignature": "(options: ModuleSourceOptions): ModuleSourceOptions", "sourceLine": 259, "examples": [ "```ts\n// Normalization: slashes stripped, relative projectRoot resolved\nconst normalized = normalizeSourceOptions({projectRoot: '.', sourcePaths: ['/src/lib/'], ...});\n// normalized.projectRoot is now absolute, normalized.sourcePaths is ['src/lib']\n```" ], "throws": [ { "type": "Error", "description": "if validation fails" } ], "parameters": [ { "name": "options", "type": "ModuleSourceOptions" } ], "returnType": "ModuleSourceOptions", "returnDescription": "a new `ModuleSourceOptions` with normalized fields" }, { "name": "getSourceRoot", "kind": "function", "docComment": "Get the effective `sourceRoot` from options.\n\nReturns `sourceRoot` if provided, otherwise:\n- Single `sourcePath`: returns that path\n- Multiple `sourcePaths`: derives the longest common directory prefix", "typeSignature": "(options: ModuleSourceOptions): string", "sourceLine": 350, "parameters": [ { "name": "options", "type": "ModuleSourceOptions" } ], "returnType": "string", "returnDescription": "the effective source root path" }, { "name": "extractPath", "kind": "function", "docComment": "Extract module path relative to source root from absolute source ID.\n\nUses proper path semantics: strips `projectRoot/sourceRoot/` prefix.", "typeSignature": "(sourceId: string, options: ModuleSourceOptions): string", "sourceLine": 385, "examples": [ "```ts\nconst options = createSourceOptions('/home/user/project');\nextractPath('/home/user/project/src/lib/foo.ts', options) // => 'foo.ts'\nextractPath('/home/user/project/src/lib/nested/bar.svelte', options) // => 'nested/bar.svelte'\n```", "```ts\nconst options = createSourceOptions('/home/user/project', {\n sourcePaths: ['src/lib', 'src/routes'],\n sourceRoot: 'src',\n});\nextractPath('/home/user/project/src/lib/foo.ts', options) // => 'lib/foo.ts'\nextractPath('/home/user/project/src/routes/page.svelte', options) // => 'routes/page.svelte'\n```" ], "parameters": [ { "name": "sourceId", "type": "string", "description": "absolute path to the source file" }, { "name": "options", "type": "ModuleSourceOptions", "description": "module source options for path extraction" } ], "returnType": "string" }, { "name": "isSource", "kind": "function", "docComment": "Check if a path is an analyzable source file.\n\nCombines all filtering: source directory paths, exclude globs, and analyzer\navailability. This is the single check for whether a file should be\nincluded in library analysis.\n\nUses proper path semantics with `startsWith` matching against\n`projectRoot/sourcePath/`. No heuristics needed — nested directories are\ncorrectly excluded by the prefix check.\n\nOrder is sourceDir-then-exclude: the prefix check guarantees the path lives\nunder `projectRoot` before relativization, so `relative()` always produces\na clean glob-shaped string for the matcher. Files outside `projectRoot`\n(rare; would require monorepo path mapping) short-circuit at the prefix\ncheck before reaching the matcher.", "typeSignature": "(path: string, options: ModuleSourceOptions): boolean", "sourceLine": 456, "examples": [ "```ts\nconst options = createSourceOptions('/home/user/project');\nisSource('/home/user/project/src/lib/foo.ts', options) // => true\nisSource('/home/user/project/src/lib/styles.css', options) // => true\nisSource('/home/user/project/src/lib/data.json', options) // => true\nisSource('/home/user/project/src/lib/foo.test.ts', options) // => false (excluded)\nisSource('/home/user/project/src/fixtures/mini/src/lib/bar.ts', options) // => false (wrong prefix)\n```" ], "parameters": [ { "name": "path", "type": "string", "description": "full absolute path to check" }, { "name": "options", "type": "ModuleSourceOptions", "description": "module source options for filtering" } ], "returnType": "boolean", "returnDescription": "true if the path is an analyzable source file" }, { "name": "extractDependencies", "kind": "function", "docComment": "Extract dependencies and dependents for a module from source file info.\n\nFilters to only include source modules (excludes external packages, node_modules, tests).\nReturns sorted arrays of module paths (relative to `sourceRoot`) for deterministic output.\n\nNative paths in `sourceFile.dependencies`/`dependents` are accepted —\n`isSource` and `extractPath` posixify their inputs, so direct callers with\nhand-built input need not pre-normalize.\n\nAccepts `SourceFileInfo` plus an optional `dependents` field — the public\ninput type carries only `dependencies` (caller-supplied opt-in), while\n`dependents` is computed downstream by `computeDependents` and flows through\nas an enriched shape.", "typeSignature": "(sourceFile: SourceFileInfo & { dependents?: readonly string[] | undefined; }, options: ModuleSourceOptions): { dependencies: string[]; dependents: string[]; }", "sourceLine": 501, "parameters": [ { "name": "sourceFile", "type": "SourceFileInfo & { dependents?: readonly string[] | undefined; }", "description": "the source file info to extract dependencies from" }, { "name": "options", "type": "ModuleSourceOptions", "description": "module source options for filtering and path extraction" } ], "returnType": "{ dependencies: string[]; dependents: string[]; }", "returnDescription": "sorted arrays of module paths (relative to `sourceRoot`) for dependencies and dependents" } ], "moduleComment": "Source configuration, path extraction, and file filtering.\n\nProvides `ModuleSourceOptions` configuration management and functions\nthat operate on source files using those options: path extraction,\nsource detection, dependency filtering, and file collection.\n\n@see `source.ts` for pure file type predicates (`isTypescript`, `isSvelte`, etc.)\n@see `analyze.ts` for consumers (`analyze`, `analyzeFromFiles`)", "dependencies": [ "paths.ts", "postprocess.ts", "source.ts" ], "dependents": [ "analyze-core.ts", "analyze.ts", "discovery.ts", "index.ts", "session.ts", "svelte.ts", "typescript-exports.ts", "typescript-program.ts", "vite.ts" ] }