/**
* One-shot analysis wrappers — `analyze` and `analyzeFromFiles`.
*
* Both wrap a single-use `AnalysisSession`: `createSession → setFiles → query
* → dispose`. Incremental consumers (Vite plugin, LSP) should use
* `createAnalysisSession` directly so parsed ASTs and svelte2tsx output
* survive between calls.
*
* - `analyzeFromFiles` — high-level: file discovery from disk + dependency
* resolution + analysis. Recommended for CLI / build-time generation.
* - `analyze` — mid-level: caller supplies `SourceFileInfo[]`. For build-tool
* integrations that own discovery but don't run incrementally.
*
* @see `session.ts` for the persistent session API
* @see `analyze-core.ts` for the two-phase orchestrator
*
* @module
*/
import {errorsOf, formatDiagnostic, warningsOf} from './diagnostics.js';
import type {AnalysisLog} from './log.js';
import {createAnalysisSession, type AnalysisSession} from './session.js';
import {
createSourceOptions,
type ModuleSourceOptions,
type SourceOptionsDefaults,
} from './source-config.js';
import {discoverSourceFiles, type Discovery} from './discovery.js';
import {noDepsResolver, type ResolveImport} from './dep-resolver.js';
import {
normalizeDiagnosticPaths,
type AnalyzeResultJson,
type OnDuplicates,
} from './analyze-core.js';
import type {SourceFileInfo} from './source.js';
// ── analyze: one-shot, caller-supplied sourceFiles ───────────────────────────
/**
* Options for `analyze`.
*
* Requires pre-loaded `SourceFileInfo` arrays — use `analyzeFromFiles` for
* automatic file discovery and loading from disk, or `createAnalysisSession`
* for incremental use.
*/
export interface AnalyzeOptions {
/** Source files to analyze (must have content loaded). */
sourceFiles: ReadonlyArray<SourceFileInfo>;
/** Module source options for path extraction and source filtering. */
sourceOptions: ModuleSourceOptions;
/** Behavior when duplicate declaration names are found across modules. */
onDuplicates?: OnDuplicates;
/** Optional logger for status and diagnostic messages. */
log?: AnalysisLog;
/**
* Optional custom import resolver for the session default — a bare
* `ResolveImportFn` or a token-paired `ImportResolver` (see `ResolveImport`).
* For one-shot `analyze()` the session is single-use, so a bare function is
* the natural form; pass an `ImportResolver` with a stable `identity` only if
* you have a reason to control the cache scope.
*/
resolveImport?: ResolveImport;
}
/**
* Analyze library source files and extract metadata (one-shot).
*
* Wraps a single-use `AnalysisSession`. For repeated analyses of the same
* source set (e.g., a Vite plugin reacting to file edits), use
* `createAnalysisSession` directly.
*
* @returns analyzed modules (sorted alphabetically) + concatenated ingest +
* query diagnostics
* @throws Error if `sourceOptions` validation fails or `tsconfig.json` is
* missing at `projectRoot`. Also propagates `onDuplicates: 'throw'` errors.
*/
export const analyze = async (options: AnalyzeOptions): Promise<AnalyzeResultJson> => {
const session = createAnalysisSession({
sourceOptions: options.sourceOptions,
log: options.log,
resolveImport: options.resolveImport,
});
try {
const ingest = await session.setFiles(options.sourceFiles);
const query = session.query({onDuplicates: options.onDuplicates, log: options.log});
// Concat is safe by spec — ingest diagnostics are paths-normalized in
// session.setFiles; query diagnostics are normalized in analyze-core.
return {
modules: query.modules,
diagnostics: [...ingest.diagnostics, ...query.diagnostics],
};
} finally {
session.dispose();
}
};
// ── analyzeFromFiles: high-level disk-discovery wrapper ──────────────────────
/**
* Options for `analyzeFromFiles`.
*
* @see `AnalyzeOptions` for the build-tool-integration API where you supply
* `sourceFiles` and a fully-formed `sourceOptions: ModuleSourceOptions` directly.
*/
export interface AnalyzeFromFilesOptions {
/** Absolute path to project root directory. */
projectRoot: string;
/** Partial overrides for default source options (SvelteKit `src/lib` layout). */
sourceOptions?: Partial<SourceOptionsDefaults>;
/** Behavior when duplicate declaration names are found across modules. */
onDuplicates?: OnDuplicates;
/** Optional logger for status and diagnostic messages. */
log?: AnalysisLog;
/**
* Glob patterns to include (relative to `projectRoot`).
*
* Filters glob-based discovery. Providing `include` under the default
* `discovery: 'auto'` collapses the chain to glob immediately; combining
* with `discovery: 'exports'` throws.
*
* When omitted, the glob fallback derives an include from
* `sourceOptions.sourcePaths` via `deriveIncludePatterns`, so custom
* `sourcePaths` (e.g., `['packages/foo']`) survive the fallback instead of
* silently defaulting to `src/lib`.
*/
include?: Array<string>;
/** Glob patterns to exclude — fully replaces `sourceOptions.exclude` (no merge). */
exclude?: Array<string>;
/**
* Whether to resolve import dependencies (default `true`).
*
* When `false`, the session uses a no-op resolver that always returns
* `null`, so `ModuleJson.dependencies` / `dependents` stay empty.
* `analyzeFromFiles`'s discovery layer does not pre-populate
* `SourceFileInfo.dependencies`, so the session's pre-resolved fast path
* isn't reachable through this API — to exercise it, drive `analyze` or
* `createAnalysisSession` directly with files whose `dependencies` field
* is already filled in by your build tool.
*
* @default `true`
*/
resolveDependencies?: boolean;
/**
* Optional custom import resolver — a bare `ResolveImportFn` or a
* token-paired `ImportResolver` (see `ResolveImport`). One-shot use doesn't
* benefit from a stable cache identity, so the bare function form is the
* natural choice here; for long-lived consumers (Vite plugin, LSP) construct
* an `ImportResolver` with a stable `identity` and pass it via
* `createAnalysisSession` so cache hits survive across calls.
*
* Cannot be combined with `resolveDependencies: false` — resolution is then
* off, so the resolver would never be consulted; passing both throws.
*/
resolveImport?: ResolveImport;
/** Discovery strategy for source files. @default 'auto' */
discovery?: Discovery;
/** Dist directory name for exports-based discovery. @default 'dist' */
distDir?: string;
}
/**
* Analyze a library from files on disk with automatic file discovery.
*
* Recommended high-level API for one-shot use (CLI, build-time generation):
*
* 1. **Discovery** — `discoverSourceFiles` (exports-first, glob fallback)
* 2. **Ingest** — push discovered files into a single-use session
* 3. **Analysis** — `session.query()`
*
* @returns analyzed modules + concatenated ingest, discovery, and query diagnostics
* @throws Error if `sourceOptions` validation fails or `tsconfig.json` is missing
*/
export const analyzeFromFiles = async (
options: AnalyzeFromFilesOptions,
): Promise<AnalyzeResultJson> => {
const {
projectRoot,
include,
exclude,
resolveDependencies: shouldResolveDependencies = true,
resolveImport,
sourceOptions,
onDuplicates,
log,
discovery,
distDir,
} = options;
// A custom resolver with resolution turned off is a contradiction — the
// resolver would never be consulted. Fail fast rather than silently dropping
// it (mirrors the `discovery: 'exports'` + `include` config-error throw).
if (!shouldResolveDependencies && resolveImport !== undefined) {
throw new Error(
'`resolveImport` cannot be combined with `resolveDependencies: false` — ' +
'dependency resolution is disabled, so the resolver would never be consulted. ' +
'Remove `resolveImport`, or set `resolveDependencies: true` (the default).',
);
}
// Top-level `exclude` is an ergonomic shortcut that fully replaces
// `sourceOptions.exclude` (no array merge). Apply before
// `createSourceOptions` so the normalized options carry a single source
// of truth.
const mergedSourceOptions = exclude !== undefined ? {...sourceOptions, exclude} : sourceOptions;
const resolvedSourceOptions = createSourceOptions(projectRoot, mergedSourceOptions);
const normalizedProjectRoot = resolvedSourceOptions.projectRoot;
// Step 1: discover files.
const {files: discoveredFiles, diagnostics: discoveryDiagnostics} = await discoverSourceFiles({
sourceOptions: resolvedSourceOptions,
include,
discovery,
distDir,
log,
});
// Step 2: build the session-default resolver. When dep-resolution is
// disabled, install the shared no-deps resolver so the session's lex+resolve
// passes surface no edges (faster than relying on the default resolver to
// return null for everything). Otherwise forward the caller's resolver — the
// session normalizes the `ResolveImport` union (bare fn or token-paired) and
// falls back to its TS+tsconfig default when undefined.
const sessionResolver: ResolveImport | undefined = shouldResolveDependencies
? resolveImport
: noDepsResolver;
// Step 3: ingest + analyze via single-use session.
const session: AnalysisSession = createAnalysisSession({
sourceOptions: resolvedSourceOptions,
log,
resolveImport: sessionResolver,
});
let result: AnalyzeResultJson;
try {
const ingest = await session.setFiles(discoveredFiles);
const query = session.query({onDuplicates, log});
result = {
modules: query.modules,
diagnostics: [...ingest.diagnostics, ...query.diagnostics],
};
} finally {
session.dispose();
}
// Merge discovery diagnostics. `analyzeCore` and the session both already
// normalized their own; normalize discovery in place (idempotent for paths
// already project-relative) and append.
normalizeDiagnosticPaths(discoveryDiagnostics, normalizedProjectRoot);
for (const d of discoveryDiagnostics) result.diagnostics.push(d);
if (log && shouldResolveDependencies) {
const totalDeps = result.modules.reduce((sum: number, m) => sum + m.dependencies.length, 0);
log.info(`Resolved ${totalDeps} module dependency edges`);
}
if (log && result.diagnostics.length > 0) {
const errors = errorsOf(result.diagnostics);
const warnings = warningsOf(result.diagnostics);
if (errors.length > 0) {
log.error(`Analysis completed with ${errors.length} error(s):`);
for (const diagnostic of errors) {
log.error(` ${formatDiagnostic(diagnostic)}`);
}
}
if (warnings.length > 0) {
log.warn(`Analysis completed with ${warnings.length} warning(s):`);
for (const diagnostic of warnings) {
log.warn(` ${formatDiagnostic(diagnostic)}`);
}
}
}
return result;
};
{
"path": "analyze.ts",
"declarations": [
{
"name": "AnalyzeOptions",
"kind": "interface",
"docComment": "Options for `analyze`.\n\nRequires pre-loaded `SourceFileInfo` arrays — use `analyzeFromFiles` for\nautomatic file discovery and loading from disk, or `createAnalysisSession`\nfor incremental use.",
"typeSignature": "AnalyzeOptions",
"sourceLine": 46,
"alsoExportedFrom": [
"index.ts"
],
"members": [
{
"name": "sourceFiles",
"kind": "variable",
"docComment": "Source files to analyze (must have content loaded).",
"typeSignature": "ReadonlyArray<SourceFileInfo>"
},
{
"name": "sourceOptions",
"kind": "variable",
"docComment": "Module source options for path extraction and source filtering.",
"typeSignature": "ModuleSourceOptions"
},
{
"name": "onDuplicates",
"kind": "variable",
"docComment": "Behavior when duplicate declaration names are found across modules.",
"typeSignature": "OnDuplicates",
"optional": true
},
{
"name": "log",
"kind": "variable",
"docComment": "Optional logger for status and diagnostic messages.",
"typeSignature": "AnalysisLog",
"optional": true
},
{
"name": "resolveImport",
"kind": "variable",
"docComment": "Optional custom import resolver for the session default — a bare\n`ResolveImportFn` or a token-paired `ImportResolver` (see `ResolveImport`).\nFor one-shot `analyze()` the session is single-use, so a bare function is\nthe natural form; pass an `ImportResolver` with a stable `identity` only if\nyou have a reason to control the cache scope.",
"typeSignature": "ResolveImport",
"optional": true
}
]
},
{
"name": "analyze",
"kind": "function",
"docComment": "Analyze library source files and extract metadata (one-shot).\n\nWraps a single-use `AnalysisSession`. For repeated analyses of the same\nsource set (e.g., a Vite plugin reacting to file edits), use\n`createAnalysisSession` directly.",
"typeSignature": "(options: AnalyzeOptions): Promise<{ modules: { path: string; declarations: ({ kind: \"function\"; parameters: { name: string; type: string; optional: boolean; rest: boolean; description?: string | undefined; defaultValue?: string | undefined; propertyDescriptions?: Record<...> | undefined; }[]; ... 17 more ...; sourceLine?: number | undefined; } | ... 7 more ... | { ...; })[]; ... 7 more ...; moduleComment?: string | undefined; }[]; diagnostics: ({ ...; } | ... 12 more ... | { ...; })[]; }>",
"sourceLine": 77,
"throws": [
{
"type": "Error",
"description": "if `sourceOptions` validation fails or `tsconfig.json` is"
}
],
"alsoExportedFrom": [
"index.ts"
],
"parameters": [
{
"name": "options",
"type": "AnalyzeOptions"
}
],
"returnType": "Promise<{ modules: { path: string; declarations: ({ kind: \"function\"; parameters: { name: string; type: string; optional: boolean; rest: boolean; description?: string | undefined; defaultValue?: string | undefined; propertyDescriptions?: Record<...> | undefined; }[]; ... 17 more ...; sourceLine?: number | undefined;...",
"returnDescription": "analyzed modules (sorted alphabetically) + concatenated ingest +\nquery diagnostics"
},
{
"name": "AnalyzeFromFilesOptions",
"kind": "interface",
"docComment": "Options for `analyzeFromFiles`.",
"typeSignature": "AnalyzeFromFilesOptions",
"sourceLine": 105,
"seeAlso": [
"`AnalyzeOptions` for the build-tool-integration API where you supply `sourceFiles` and a fully-formed `sourceOptions: ModuleSourceOptions` directly."
],
"alsoExportedFrom": [
"index.ts"
],
"members": [
{
"name": "projectRoot",
"kind": "variable",
"docComment": "Absolute path to project root directory.",
"typeSignature": "string"
},
{
"name": "sourceOptions",
"kind": "variable",
"docComment": "Partial overrides for default source options (SvelteKit `src/lib` layout).",
"typeSignature": "Partial<SourceOptionsDefaults>",
"optional": true
},
{
"name": "onDuplicates",
"kind": "variable",
"docComment": "Behavior when duplicate declaration names are found across modules.",
"typeSignature": "OnDuplicates",
"optional": true
},
{
"name": "log",
"kind": "variable",
"docComment": "Optional logger for status and diagnostic messages.",
"typeSignature": "AnalysisLog",
"optional": true
},
{
"name": "include",
"kind": "variable",
"docComment": "Glob patterns to include (relative to `projectRoot`).\n\nFilters glob-based discovery. Providing `include` under the default\n`discovery: 'auto'` collapses the chain to glob immediately; combining\nwith `discovery: 'exports'` throws.\n\nWhen omitted, the glob fallback derives an include from\n`sourceOptions.sourcePaths` via `deriveIncludePatterns`, so custom\n`sourcePaths` (e.g., `['packages/foo']`) survive the fallback instead of\nsilently defaulting to `src/lib`.",
"typeSignature": "Array<string>",
"optional": true
},
{
"name": "exclude",
"kind": "variable",
"docComment": "Glob patterns to exclude — fully replaces `sourceOptions.exclude` (no merge).",
"typeSignature": "Array<string>",
"optional": true
},
{
"name": "resolveDependencies",
"kind": "variable",
"docComment": "Whether to resolve import dependencies (default `true`).\n\nWhen `false`, the session uses a no-op resolver that always returns\n`null`, so `ModuleJson.dependencies` / `dependents` stay empty.\n`analyzeFromFiles`'s discovery layer does not pre-populate\n`SourceFileInfo.dependencies`, so the session's pre-resolved fast path\nisn't reachable through this API — to exercise it, drive `analyze` or\n`createAnalysisSession` directly with files whose `dependencies` field\nis already filled in by your build tool.",
"typeSignature": "boolean",
"optional": true,
"defaultValue": "`true`"
},
{
"name": "resolveImport",
"kind": "variable",
"docComment": "Optional custom import resolver — a bare `ResolveImportFn` or a\ntoken-paired `ImportResolver` (see `ResolveImport`). One-shot use doesn't\nbenefit from a stable cache identity, so the bare function form is the\nnatural choice here; for long-lived consumers (Vite plugin, LSP) construct\nan `ImportResolver` with a stable `identity` and pass it via\n`createAnalysisSession` so cache hits survive across calls.\n\nCannot be combined with `resolveDependencies: false` — resolution is then\noff, so the resolver would never be consulted; passing both throws.",
"typeSignature": "ResolveImport",
"optional": true
},
{
"name": "discovery",
"kind": "variable",
"docComment": "Discovery strategy for source files.",
"typeSignature": "Discovery",
"optional": true,
"defaultValue": "'auto'"
},
{
"name": "distDir",
"kind": "variable",
"docComment": "Dist directory name for exports-based discovery.",
"typeSignature": "string",
"optional": true,
"defaultValue": "'dist'"
}
]
},
{
"name": "analyzeFromFiles",
"kind": "function",
"docComment": "Analyze a library from files on disk with automatic file discovery.\n\nRecommended high-level API for one-shot use (CLI, build-time generation):\n\n1. **Discovery** — `discoverSourceFiles` (exports-first, glob fallback)\n2. **Ingest** — push discovered files into a single-use session\n3. **Analysis** — `session.query()`",
"typeSignature": "(options: AnalyzeFromFilesOptions): Promise<{ modules: { path: string; declarations: ({ kind: \"function\"; parameters: { name: string; type: string; optional: boolean; rest: boolean; description?: string | undefined; defaultValue?: string | undefined; propertyDescriptions?: Record<...> | undefined; }[]; ... 17 more ...; sourceLine?: number | undefined; } | ... 7 more ... | { ...; })[]; ... 7 more ...; moduleComment?: string | undefined; }[]; diagnostics: ({ ...; } | ... 12 more ... | { ...; })[]; }>",
"sourceLine": 173,
"throws": [
{
"type": "Error",
"description": "if `sourceOptions` validation fails or `tsconfig.json` is missing"
}
],
"alsoExportedFrom": [
"index.ts"
],
"parameters": [
{
"name": "options",
"type": "AnalyzeFromFilesOptions"
}
],
"returnType": "Promise<{ modules: { path: string; declarations: ({ kind: \"function\"; parameters: { name: string; type: string; optional: boolean; rest: boolean; description?: string | undefined; defaultValue?: string | undefined; propertyDescriptions?: Record<...> | undefined; }[]; ... 17 more ...; sourceLine?: number | undefined;...",
"returnDescription": "analyzed modules + concatenated ingest, discovery, and query diagnostics"
}
],
"moduleComment": "One-shot analysis wrappers — `analyze` and `analyzeFromFiles`.\n\nBoth wrap a single-use `AnalysisSession`: `createSession → setFiles → query\n→ dispose`. Incremental consumers (Vite plugin, LSP) should use\n`createAnalysisSession` directly so parsed ASTs and svelte2tsx output\nsurvive between calls.\n\n- `analyzeFromFiles` — high-level: file discovery from disk + dependency\n resolution + analysis. Recommended for CLI / build-time generation.\n- `analyze` — mid-level: caller supplies `SourceFileInfo[]`. For build-tool\n integrations that own discovery but don't run incrementally.\n\n@see `session.ts` for the persistent session API\n@see `analyze-core.ts` for the two-phase orchestrator",
"dependencies": [
"analyze-core.ts",
"dep-resolver.ts",
"diagnostics.ts",
"discovery.ts",
"log.ts",
"session.ts",
"source-config.ts",
"source.ts"
],
"dependents": [
"cli.ts",
"index.ts"
]
}