/** * Pure two-phase analysis orchestrator. * * Extracted from `analyze.ts` to break what would otherwise be a circular * import: `session.ts` → `analyze.ts` (for the orchestrator) → `session.ts` * (for the wrappers). With the orchestrator here, both `session.ts` and * `analyze.ts` import downward into this module without depending on each * other. * * Public surface: * * - `analyzeCore` — runs phase 1 module dispatch + phase 2 re-export merge, * returns `AnalyzeResultJson`. Caller supplies pre-prepared inputs (program, * svelte virtuals, transform-failed IDs). * - `analyzeModule` — the per-module dispatcher (TS / Svelte / CSS / JSON). * `@internal`; exposed for tests and power users via the subpath. * - `AnalyzeResultJson` / `OnDuplicates` / `OnDuplicatesCallback` — shared types * surfaced through the main barrel directly from this module. * - `throwOnDuplicates` — convenience callback paired with `OnDuplicates`. * - `normalizeDiagnosticPaths` — boundary helper for build-tool integrations * that bypass the session and collect their own diagnostics. * * @internal — module split is implementation detail; consumers go through * `analyze.ts` / `session.ts` for the stable surface. * * @module */ import ts from 'typescript'; import {z} from 'zod'; import {ModuleJson, type ModuleJsonInput} from './types.js'; import type {ModuleAnalysis} from './declaration-build.js'; import {Diagnostic} from './diagnostics.js'; import type {AnalysisLog} from './log.js'; import {analyzeTypescriptModule} from './typescript-exports.js'; import {analyzeSvelteModule, type SvelteVirtualFile} from './svelte.js'; import {stripVirtualSuffix, type SourceFileInfo, getComponentName} from './source.js'; import {type ModuleSourceOptions, extractPath, extractDependencies} from './source-config.js'; import {toPosixPath} from './paths.js'; import { sortModules, findDuplicates, mergeReExports, resolveComponentAliases, compareStrings, type DuplicateDeclaration, } from './postprocess.js'; // ── Duplicate handling ─────────────────────────────────────────────────────── /** * Custom callback for handling duplicate declaration names. * * Use the `'throw'` or `'warn'` shortcuts on `onDuplicates` for the common * cases. Pass a function to fully control reporting. * * @param duplicates - map of declaration names to their `DuplicateDeclaration` occurrences across modules * @param log - logger for reporting (only `error` method required) */ export type OnDuplicatesCallback = ( duplicates: Map<string, Array<DuplicateDeclaration>>, log: Pick<AnalysisLog, 'error'>, ) => void; /** * Behavior selector for duplicate declaration names across modules. * * - `'throw'` — throw an `Error` listing every duplicate (strict flat-namespace enforcement) * - `'warn'` — log to `log.error` and continue * - `OnDuplicatesCallback` — custom handler * * Omitted entirely: no dispatch runs, but a `duplicate_declaration` diagnostic * is still emitted into the diagnostics array for every collision (the diagnostic * is the data; this option is the action). * * **`'throw'` trade-off**: the throw fires after diagnostics are emitted but * before the result is returned, so `'throw'` callers never reach the * diagnostics array. Callers that want fail-fast *and* diagnostic access * should omit `onDuplicates` and inspect themselves: * * ```ts * const result = await analyze({...}); * if (hasErrors(result.diagnostics)) throw new Error('analysis errors'); * ``` * * Or use an `OnDuplicatesCallback` and stash the data before throwing. */ export type OnDuplicates = 'throw' | 'warn' | OnDuplicatesCallback; /** * Convenience `OnDuplicatesCallback` that throws on any duplicate. * * @throws Error listing every duplicate name and module location */ export const throwOnDuplicates: OnDuplicatesCallback = (duplicates) => { if (duplicates.size === 0) return; throw new Error(formatDuplicates(duplicates)); }; const formatDuplicates = (duplicates: Map<string, Array<DuplicateDeclaration>>): string => { const details = Array.from(duplicates) .map(([name, occurrences]) => { const locations = occurrences .map(({declaration, module}) => { const lineInfo = declaration.sourceLine !== undefined ? `:${declaration.sourceLine}` : ''; return ` - ${module}${lineInfo} (${declaration.kind})`; }) .join('\n'); return ` "${name}" found in:\n${locations}`; }) .join('\n'); return ( `Found ${duplicates.size} duplicate declaration name${duplicates.size === 1 ? '' : 's'} across modules. ` + 'The flat namespace requires unique names. To resolve: ' + '(1) rename one of the conflicting declarations, or ' + '(2) add /** @nodocs */ to exclude from documentation.\n' + details ); }; const emitDuplicateDiagnostics = ( diagnostics: Array<Diagnostic>, duplicates: Map<string, Array<DuplicateDeclaration>>, ): void => { for (const [name, occurrences] of duplicates) { const first = occurrences[0]!; const modules = occurrences.map((o) => o.module); diagnostics.push({ kind: 'duplicate_declaration', file: first.module, line: first.declaration.sourceLine, message: `Duplicate declaration "${name}" defined in: ${modules.join(', ')}`, severity: 'warning', declarationName: name, modules, }); } }; const dispatchOnDuplicates = ( mode: OnDuplicates, duplicates: Map<string, Array<DuplicateDeclaration>>, log?: AnalysisLog, ): void => { if (duplicates.size === 0) return; if (mode === 'throw') { throw new Error(formatDuplicates(duplicates)); } const errorLog: Pick<AnalysisLog, 'error'> = log ?? {error: (msg) => console.error(msg)}; if (mode === 'warn') { errorLog.error(formatDuplicates(duplicates)); return; } mode(duplicates, errorLog); }; // ── Result types ───────────────────────────────────────────────────────────── /** * Result of `analyze`, `analyzeFromFiles`, and `AnalysisSession.query`. * * Modules sorted alphabetically by `path`. Diagnostics are query-time * (analysis-pass) diagnostics only when produced by `session.query`; one-shot * wrappers concatenate ingest + query diagnostics into this same array. * * ## Schema-validated round-trip * * The envelope is a Zod schema (`AnalyzeResultJson`) — both fields default to * `[]`, so `JSON.stringify(result, compactReplacer)` strips empty arrays on * the wire and `AnalyzeResultJson.parse(JSON.parse(json))` restores them. * Consumers programmatically ingesting analysis JSON should parse through the * schema to get defaults restored; raw-JSON consumers (e.g., `jq`) treat * missing keys as null-equivalent (`jq '.diagnostics | length'` returns `0` * on `{}`) and don't need the parse step. * * Construction sites (one-shot wrappers, `session.query`) hand back hand-built * objects without re-running `.parse()` — the inner `modules` and * `diagnostics` arrays are already Zod-validated upstream, and the envelope * schema is the type contract, not a validation gate. * * See `AnalyzeResultJsonWire` for the serialized input-side shape published on * `virtual:svelte-docinfo`. */ export const AnalyzeResultJson = z.strictObject({ modules: z.array(ModuleJson).default([]), diagnostics: z.array(Diagnostic).default([]), }); export type AnalyzeResultJson = z.infer<typeof AnalyzeResultJson>; /** * Serialized wire shape of an analysis result, as published by the Vite plugin * on `virtual:svelte-docinfo` — the input-side counterpart to * `AnalyzeResultJson` (the validated output of `.parse()`). * * The two fields are deliberately asymmetric: * * - `modules` is `ModuleJsonInput` (the `z.input` of `ModuleJson`) because the * plugin runs it through `compactReplacer`, which strips `.default([])` * arrays and `.default(false)` booleans. Default-bearing fields therefore * arrive `undefined`. * - `diagnostics` is the output `Diagnostic` — the plugin serializes it * without the replacer, and `Diagnostic` has no defaults to strip, so the * array is always present and the shape matches runtime exactly. * * Consumers restore defaults by parsing through `AnalyzeResultJson`. * * Note this describes the Vite virtual module specifically. The CLI runs the * whole envelope through `compactReplacer`, so its JSON may additionally omit * an empty `diagnostics` array — CLI consumers should parse through * `AnalyzeResultJson` rather than assume this shape. */ export interface AnalyzeResultJsonWire { modules: Array<ModuleJsonInput>; diagnostics: Array<Diagnostic>; } const toModuleJson = (raw: ModuleAnalysis): ModuleJson => { const filtered = raw.declarations.filter((d) => !d.nodocs).map((d) => d.declaration); // sorted for deterministic, environment-independent output — // getExportsOfModule order is a TS implementation detail. Module tie-break // because names can collide: a Svelte default-slot re-export re-keys to // the component name, which a same-name re-export from another module may // also use. The same re-keying can produce exact-duplicate edges // (component + same-name script-module export from one file) — deduped on // `(name, module)` so those pairs stay unique; the sourceLine tie-break // makes the dedup deterministic (smallest line survives) const reExports = raw.reExports .slice() .sort( (a, b) => compareStrings(a.name, b.name) || compareStrings(a.module, b.module) || (a.sourceLine ?? 0) - (b.sourceLine ?? 0), ) .filter( (r, i, arr) => i === 0 || r.name !== arr[i - 1]!.name || r.module !== arr[i - 1]!.module, ); const externalReExports = raw.externalReExports .slice() .sort( (a, b) => compareStrings(a.name, b.name) || compareStrings(a.specifier, b.specifier) || (a.sourceLine ?? 0) - (b.sourceLine ?? 0), ); return ModuleJson.parse({ path: raw.path, declarations: filtered, dependencies: raw.dependencies, dependents: raw.dependents, starExports: raw.starExports, reExports, externalReExports, externalStarExports: raw.externalStarExports, ...(raw.moduleComment ? {moduleComment: raw.moduleComment} : {}), }); }; // ── Module dispatch ────────────────────────────────────────────────────────── /** * Analyze a single non-Svelte source file and extract module metadata. * * @internal Single-module dispatcher used internally by the two-phase * orchestrator. Importable from `svelte-docinfo/analyze-core.js` for tests * and power users — not part of the stable barrel API and not guaranteed * across minor versions. * * Dispatches on file type: * - TypeScript/JS → `analyzeTypescriptModule` * - CSS/JSON → minimal module (no declarations, dependency tracking only) * - Svelte → `module_skipped: requires_program` (callers using session API * should not reach this path; the session pre-handles Svelte) * * Returns `undefined` when the file is skipped; the diagnostic is added to * `diagnostics` so the caller can keep iterating. * * `alsoExportedFrom` on returned declarations is always empty from this path — * cross-module re-export resolution requires all modules (`mergeReExports` * consumes the returned module's `reExports` in phase 2). */ export const analyzeModule = ( sourceFile: SourceFileInfo & {dependents?: ReadonlyArray<string>}, program: ts.Program, options: ModuleSourceOptions, diagnostics: Array<Diagnostic>, log?: AnalysisLog, ): ModuleJson | undefined => { const checker = program.getTypeChecker(); const modulePath = extractPath(sourceFile.id, options); const analyzerType = options.getAnalyzerType(sourceFile.id); let raw: ModuleAnalysis | undefined; if (analyzerType === 'svelte') { diagnostics.push({ kind: 'module_skipped', file: modulePath, message: 'Svelte files require program integration. Use createAnalysisSession or analyze()/analyzeFromFiles() instead.', severity: 'warning', reason: 'requires_program', }); log?.warn(`Svelte file skipped in analyzeModule: ${sourceFile.id}`); return undefined; } else if (analyzerType === 'typescript') { const tsSourceFile = program.getSourceFile(sourceFile.id); if (!tsSourceFile) { diagnostics.push({ kind: 'module_skipped', file: modulePath, message: `Could not get source file from program: ${sourceFile.id}`, severity: 'warning', reason: 'not_in_program', }); log?.warn(`Could not get source file from program: ${sourceFile.id}`); return undefined; } raw = analyzeTypescriptModule( sourceFile, tsSourceFile, modulePath, checker, options, diagnostics, ); } else if (analyzerType === 'css' || analyzerType === 'json') { const {dependencies, dependents} = extractDependencies(sourceFile, options); raw = { path: modulePath, declarations: [], dependencies, dependents, starExports: [], reExports: [], externalReExports: [], externalStarExports: [], }; } else { diagnostics.push({ kind: 'module_skipped', file: modulePath, message: `No analyzer for file type: ${sourceFile.id}`, severity: 'warning', reason: 'no_analyzer', }); log?.warn(`No analyzer for file: ${sourceFile.id}`); return undefined; } return toModuleJson(raw); }; // ── Core two-phase loop ────────────────────────────────────────────────────── /** * Inputs to `analyzeCore`. The caller (one-shot wrapper or session.query) * is responsible for normalizing `sourceOptions`, obtaining the program, * and pre-transforming Svelte files into `svelteVirtualFiles`. * * `transformFailedIds` carries the IDs of `.svelte` files whose svelte2tsx * transform threw at ingest. The dispatch synthesizes a placeholder * `ModuleJson` (`partial: true`, empty declarations) for each so consumers * see the file's existence in `modules` even though analysis couldn't run. * Identifying these via a sibling Set keeps `svelteVirtualFiles` a clean * "files we can analyze" map; the failure side-channel doesn't pollute it. */ export interface AnalyzeCoreInputs { sourceFiles: ReadonlyArray<SourceFileInfo>; sourceOptions: ModuleSourceOptions; program: ts.Program; svelteVirtualFiles: ReadonlyMap<string, SvelteVirtualFile>; /** Svelte file IDs whose svelte2tsx transform failed at ingest. */ transformFailedIds?: ReadonlySet<string>; onDuplicates?: OnDuplicates; log?: AnalysisLog; } /** * Run the two-phase analysis loop on pre-prepared inputs. * * @internal Shared two-phase orchestrator used internally by `session.query` * and the one-shot wrappers. Importable from `svelte-docinfo/analyze-core.js` * for tests and power users — not part of the stable barrel API and not * guaranteed across minor versions. * * Phase 1: per-file dispatch (TS / Svelte / CSS / JSON / placeholder for * transform-failed Svelte). Phase 2: re-export merge, component-alias fill, * sort, duplicate detection. Diagnostic paths are normalized to * project-root-relative form before return. * * Dependents are read from `sourceFile.dependents` (caller-supplied via * `extractDependencies`); `analyzeCore` does not compute them. `session.query` * runs `computeDependents` on the owned set before invoking this. */ export const analyzeCore = (inputs: AnalyzeCoreInputs): AnalyzeResultJson => { const { sourceFiles, sourceOptions, program, svelteVirtualFiles, transformFailedIds, onDuplicates, log, } = inputs; const checker = program.getTypeChecker(); const diagnostics: Array<Diagnostic> = []; const modules: Array<ModuleJson> = []; // Phase 1: analyze every module (forward re-export edges land on `ModuleJson.reExports`) for (const sourceFile of sourceFiles) { // Failed-transform Svelte file: synthesize placeholder ModuleJson so // the modules array reflects the full owned set. The originating // transform_failed ingest diagnostic carries the cause; this slot // is purely structural. if (transformFailedIds?.has(sourceFile.id)) { const modulePath = extractPath(sourceFile.id, sourceOptions); const componentName = getComponentName(modulePath); const {dependencies, dependents} = extractDependencies(sourceFile, sourceOptions); modules.push( ModuleJson.parse({ path: modulePath, declarations: [], dependencies, dependents, starExports: [], partial: true, }), ); log?.warn(`Svelte component ${componentName} marked partial (transform failed at ingest)`); continue; } let mod: ModuleJson | undefined; const virtualFile = svelteVirtualFiles.get(sourceFile.id); if (virtualFile) { const modulePath = extractPath(sourceFile.id, sourceOptions); const raw = analyzeSvelteModule( sourceFile, modulePath, checker, sourceOptions, diagnostics, program, virtualFile, ); if (raw) { mod = toModuleJson(raw); } else { log?.error(`Svelte module analysis failed: ${sourceFile.id}`); } } else { mod = analyzeModule(sourceFile, program, sourceOptions, diagnostics, log); } if (!mod) continue; modules.push(mod); } // Phase 2a: build alsoExportedFrom arrays from the modules' forward edges mergeReExports(modules); // Phase 2b: fill component-only fields on renamed component aliases — // canonical components are only fully populated after phase 1 finishes. resolveComponentAliases(modules); const sortedModules = sortModules(modules); // Always run duplicate detection so a `duplicate_declaration` diagnostic // reaches consumers regardless of `onDuplicates`. The `onDuplicates` // callback/shortcut still fires for callers that want fail-fast or custom // handling — diagnostics is the data, `onDuplicates` is the action. const duplicates = findDuplicates(sortedModules); emitDuplicateDiagnostics(diagnostics, duplicates); if (onDuplicates) { dispatchOnDuplicates(onDuplicates, duplicates, log); } normalizeDiagnosticPaths(diagnostics, sourceOptions.projectRoot); return { modules: sortedModules, diagnostics, }; }; /** * Normalize `Diagnostic.file` to project-root-relative form, in place. * * Producers inside the analysis pipeline can write absolute paths or virtual * paths (svelte2tsx output like `Foo.svelte.__svelte2tsx__.ts`). This pass * collapses both to the public contract: a path relative to `projectRoot` * with no leading slash and no `./` prefix. * * Exposed for build-tool integrations that bypass the session and collect * their own discovery/dep diagnostics — they need the same normalization to * match the public contract. * * @mutates diagnostics — rewrites each diagnostic's `file` field */ export const normalizeDiagnosticPaths = ( diagnostics: Array<Diagnostic>, projectRoot: string, ): void => { const prefix = projectRoot.endsWith('/') ? projectRoot : projectRoot + '/'; for (const d of diagnostics) { // Posixify so producers that emitted native-separator paths (e.g., a // custom discovery layer building `path.relative` results on Windows) // match the POSIX prefix derived from the normalized projectRoot. let file = toPosixPath(stripVirtualSuffix(d.file)); if (file.startsWith(prefix)) { file = file.slice(prefix.length); } else if (file.startsWith('/')) { // Absolute path outside projectRoot — drop leading slash so display // stays consistent. file = file.slice(1); } d.file = file; } };
{ "path": "analyze-core.ts", "declarations": [ { "name": "OnDuplicatesCallback", "kind": "type", "docComment": "Custom callback for handling duplicate declaration names.\n\nUse the `'throw'` or `'warn'` shortcuts on `onDuplicates` for the common\ncases. Pass a function to fully control reporting.", "typeSignature": "OnDuplicatesCallback", "sourceLine": 61, "alsoExportedFrom": [ "index.ts" ], "members": [ { "name": "(call)", "kind": "function", "typeSignature": "(duplicates: Map<string, DuplicateDeclaration[]>, log: Pick<AnalysisLog, \"error\">): void", "parameters": [ { "name": "duplicates", "type": "Map<string, DuplicateDeclaration[]>" }, { "name": "log", "type": "Pick<AnalysisLog, \"error\">" } ], "returnType": "void" } ] }, { "name": "OnDuplicates", "kind": "type", "docComment": "Behavior selector for duplicate declaration names across modules.\n\n- `'throw'` — throw an `Error` listing every duplicate (strict flat-namespace enforcement)\n- `'warn'` — log to `log.error` and continue\n- `OnDuplicatesCallback` — custom handler\n\nOmitted entirely: no dispatch runs, but a `duplicate_declaration` diagnostic\nis still emitted into the diagnostics array for every collision (the diagnostic\nis the data; this option is the action).\n\n**`'throw'` trade-off**: the throw fires after diagnostics are emitted but\nbefore the result is returned, so `'throw'` callers never reach the\ndiagnostics array. Callers that want fail-fast *and* diagnostic access\nshould omit `onDuplicates` and inspect themselves:\n\n```ts\nconst result = await analyze({...});\nif (hasErrors(result.diagnostics)) throw new Error('analysis errors');\n```\n\nOr use an `OnDuplicatesCallback` and stash the data before throwing.", "typeSignature": "OnDuplicates", "sourceLine": 89, "alsoExportedFrom": [ "index.ts" ] }, { "name": "throwOnDuplicates", "kind": "function", "docComment": "Convenience `OnDuplicatesCallback` that throws on any duplicate.", "typeSignature": "(duplicates: Map<string, DuplicateDeclaration[]>, log: Pick<AnalysisLog, \"error\">): void", "sourceLine": 96, "throws": [ { "type": "Error", "description": "listing every duplicate name and module location" } ], "alsoExportedFrom": [ "index.ts" ], "parameters": [ { "name": "duplicates", "type": "Map<string, DuplicateDeclaration[]>" }, { "name": "log", "type": "Pick<AnalysisLog, \"error\">" } ], "returnType": "void" }, { "name": "AnalyzeResultJson", "kind": "type", "docComment": "Result of `analyze`, `analyzeFromFiles`, and `AnalysisSession.query`.\n\nModules sorted alphabetically by `path`. Diagnostics are query-time\n(analysis-pass) diagnostics only when produced by `session.query`; one-shot\nwrappers concatenate ingest + query diagnostics into this same array.\n\n## Schema-validated round-trip\n\nThe envelope is a Zod schema (`AnalyzeResultJson`) — both fields default to\n`[]`, so `JSON.stringify(result, compactReplacer)` strips empty arrays on\nthe wire and `AnalyzeResultJson.parse(JSON.parse(json))` restores them.\nConsumers programmatically ingesting analysis JSON should parse through the\nschema to get defaults restored; raw-JSON consumers (e.g., `jq`) treat\nmissing keys as null-equivalent (`jq '.diagnostics | length'` returns `0`\non `{}`) and don't need the parse step.\n\nConstruction sites (one-shot wrappers, `session.query`) hand back hand-built\nobjects without re-running `.parse()` — the inner `modules` and\n`diagnostics` arrays are already Zod-validated upstream, and the envelope\nschema is the type contract, not a validation gate.\n\nSee `AnalyzeResultJsonWire` for the serialized input-side shape published on\n`virtual:svelte-docinfo`.", "typeSignature": "ZodObject<{ modules: ZodDefault<ZodArray<ZodObject<{ path: ZodString; declarations: ZodDefault<ZodArray<ZodDiscriminatedUnion<[ZodObject<{ kind: ZodLiteral<\"function\">; returnType: ZodOptional<ZodString>; returnDescription: ZodOptional<...>; ... 16 more ...; genericParams: ZodDefault<...>; }, $strict>, ... 7 more .....", "sourceLine": 186, "alsoExportedFrom": [ "index.ts" ] }, { "name": "AnalyzeResultJsonWire", "kind": "interface", "docComment": "Serialized wire shape of an analysis result, as published by the Vite plugin\non `virtual:svelte-docinfo` — the input-side counterpart to\n`AnalyzeResultJson` (the validated output of `.parse()`).\n\nThe two fields are deliberately asymmetric:\n\n- `modules` is `ModuleJsonInput` (the `z.input` of `ModuleJson`) because the\n plugin runs it through `compactReplacer`, which strips `.default([])`\n arrays and `.default(false)` booleans. Default-bearing fields therefore\n arrive `undefined`.\n- `diagnostics` is the output `Diagnostic` — the plugin serializes it\n without the replacer, and `Diagnostic` has no defaults to strip, so the\n array is always present and the shape matches runtime exactly.\n\nConsumers restore defaults by parsing through `AnalyzeResultJson`.\n\nNote this describes the Vite virtual module specifically. The CLI runs the\nwhole envelope through `compactReplacer`, so its JSON may additionally omit\nan empty `diagnostics` array — CLI consumers should parse through\n`AnalyzeResultJson` rather than assume this shape.", "typeSignature": "AnalyzeResultJsonWire", "sourceLine": 214, "alsoExportedFrom": [ "index.ts" ], "members": [ { "name": "modules", "kind": "variable", "typeSignature": "Array<ModuleJsonInput>" }, { "name": "diagnostics", "kind": "variable", "typeSignature": "Array<Diagnostic>" } ] }, { "name": "analyzeModule", "kind": "function", "docComment": "Analyze a single non-Svelte source file and extract module metadata.", "typeSignature": "(sourceFile: SourceFileInfo & { dependents?: readonly string[] | undefined; }, program: Program, options: ModuleSourceOptions, diagnostics: ({ symbolName: string; ... 5 more ...; column?: number | undefined; } | ... 12 more ... | { ...; })[], log?: AnalysisLog | undefined): { ...; } | undefined", "sourceLine": 284, "parameters": [ { "name": "sourceFile", "type": "SourceFileInfo & { dependents?: readonly string[] | undefined; }" }, { "name": "program", "type": "Program" }, { "name": "options", "type": "ModuleSourceOptions" }, { "name": "diagnostics", "type": "({ symbolName: string; file: string; message: string; severity: \"error\" | \"warning\"; kind: \"type_extraction_failed\"; line?: number | undefined; column?: number | undefined; } | { functionName: string; ... 5 more ...; column?: number | undefined; } | ... 11 more ... | { ...; })[]" }, { "name": "log", "type": "AnalysisLog | undefined", "optional": true } ], "returnType": "{ 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 ......" }, { "name": "AnalyzeCoreInputs", "kind": "interface", "docComment": "Inputs to `analyzeCore`. The caller (one-shot wrapper or session.query)\nis responsible for normalizing `sourceOptions`, obtaining the program,\nand pre-transforming Svelte files into `svelteVirtualFiles`.\n\n`transformFailedIds` carries the IDs of `.svelte` files whose svelte2tsx\ntransform threw at ingest. The dispatch synthesizes a placeholder\n`ModuleJson` (`partial: true`, empty declarations) for each so consumers\nsee the file's existence in `modules` even though analysis couldn't run.\nIdentifying these via a sibling Set keeps `svelteVirtualFiles` a clean\n\"files we can analyze\" map; the failure side-channel doesn't pollute it.", "typeSignature": "AnalyzeCoreInputs", "sourceLine": 370, "members": [ { "name": "sourceFiles", "kind": "variable", "typeSignature": "ReadonlyArray<SourceFileInfo>" }, { "name": "sourceOptions", "kind": "variable", "typeSignature": "ModuleSourceOptions" }, { "name": "program", "kind": "variable", "typeSignature": "ts.Program" }, { "name": "svelteVirtualFiles", "kind": "variable", "typeSignature": "ReadonlyMap<string, SvelteVirtualFile>" }, { "name": "transformFailedIds", "kind": "variable", "docComment": "Svelte file IDs whose svelte2tsx transform failed at ingest.", "typeSignature": "ReadonlySet<string>", "optional": true }, { "name": "onDuplicates", "kind": "variable", "typeSignature": "OnDuplicates", "optional": true }, { "name": "log", "kind": "variable", "typeSignature": "AnalysisLog", "optional": true } ] }, { "name": "analyzeCore", "kind": "function", "docComment": "Run the two-phase analysis loop on pre-prepared inputs.", "typeSignature": "(inputs: AnalyzeCoreInputs): { 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": 398, "parameters": [ { "name": "inputs", "type": "AnalyzeCoreInputs" } ], "returnType": "{ 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; } | ......" }, { "name": "normalizeDiagnosticPaths", "kind": "function", "docComment": "Normalize `Diagnostic.file` to project-root-relative form, in place.\n\nProducers inside the analysis pipeline can write absolute paths or virtual\npaths (svelte2tsx output like `Foo.svelte.__svelte2tsx__.ts`). This pass\ncollapses both to the public contract: a path relative to `projectRoot`\nwith no leading slash and no `./` prefix.\n\nExposed for build-tool integrations that bypass the session and collect\ntheir own discovery/dep diagnostics — they need the same normalization to\nmatch the public contract.", "typeSignature": "(diagnostics: ({ symbolName: string; file: string; message: string; severity: \"error\" | \"warning\"; kind: \"type_extraction_failed\"; line?: number | undefined; column?: number | undefined; } | { functionName: string; ... 5 more ...; column?: number | undefined; } | ... 11 more ... | { ...; })[], projectRoot: string): void", "sourceLine": 506, "mutates": { "diagnostics": "— rewrites each diagnostic's `file` field" }, "parameters": [ { "name": "diagnostics", "type": "({ symbolName: string; file: string; message: string; severity: \"error\" | \"warning\"; kind: \"type_extraction_failed\"; line?: number | undefined; column?: number | undefined; } | { functionName: string; ... 5 more ...; column?: number | undefined; } | ... 11 more ... | { ...; })[]" }, { "name": "projectRoot", "type": "string" } ], "returnType": "void" } ], "moduleComment": "Pure two-phase analysis orchestrator.\n\nExtracted from `analyze.ts` to break what would otherwise be a circular\nimport: `session.ts` → `analyze.ts` (for the orchestrator) → `session.ts`\n(for the wrappers). With the orchestrator here, both `session.ts` and\n`analyze.ts` import downward into this module without depending on each\nother.\n\nPublic surface:\n\n- `analyzeCore` — runs phase 1 module dispatch + phase 2 re-export merge,\n returns `AnalyzeResultJson`. Caller supplies pre-prepared inputs (program,\n svelte virtuals, transform-failed IDs).\n- `analyzeModule` — the per-module dispatcher (TS / Svelte / CSS / JSON).\n `@internal`; exposed for tests and power users via the subpath.\n- `AnalyzeResultJson` / `OnDuplicates` / `OnDuplicatesCallback` — shared types\n surfaced through the main barrel directly from this module.\n- `throwOnDuplicates` — convenience callback paired with `OnDuplicates`.\n- `normalizeDiagnosticPaths` — boundary helper for build-tool integrations\n that bypass the session and collect their own diagnostics.\n\n@internal — module split is implementation detail; consumers go through\n`analyze.ts` / `session.ts` for the stable surface.", "dependencies": [ "declaration-build.ts", "diagnostics.ts", "log.ts", "paths.ts", "postprocess.ts", "source-config.ts", "source.ts", "svelte.ts", "types.ts", "typescript-exports.ts" ], "dependents": [ "analyze.ts", "cli.ts", "index.ts", "session.ts", "vite.ts" ] }