/**
* Source file type predicates and path helpers.
*
* Pure functions for detecting file types by extension and extracting
* component names from paths. No configuration dependency — these are
* the building blocks used by `source-config.ts`.
*
* @see `source-config.ts` for configuration-aware functions (`isSource`, `extractPath`, etc.)
* @see `analyze.ts` for consumers (`analyze`, `analyzeFromFiles`)
*
* @module
*/
/**
* Analyzer type for source files.
*
* - `'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
*/
export type AnalyzerType = 'typescript' | 'svelte' | 'css' | 'json';
/**
* File information for source analysis.
*
* Provides file content to analysis functions from any source:
* file system, build pipeline, or in-memory.
*
* Note: `content` is required to keep analysis functions pure (no hidden I/O).
* Callers are responsible for reading file content before analysis.
*/
export interface SourceFileInfo {
/** Absolute path to the file. */
id: string;
/** File content (required - analysis functions don't read from disk). */
content: string;
/**
* Pre-resolved absolute file paths of modules this file imports.
*
* **Opt-in optimization** — when supplied, the session treats this as the
* authoritative dependency set for the file and skips its own lex+resolve
* pass for this entry. Build-tool integrations (e.g., Gro's filer) that
* already maintain a dependency graph can hand it over directly instead of
* paying the lex+resolve cost twice.
*
* **Omit (undefined) → default behavior** — the session lexes import
* specifiers from `content` and resolves them via its `ImportResolver`.
* This is the right choice when the caller doesn't already have a graph.
*
* Only include resolved local imports — node_modules paths are filtered
* out at storage time by the configured `isSource` predicate either way.
*
* **Trust contract** — the session treats this array as authoritative and
* does not cross-check against the file's `content`. Edges declared here
* are accepted as-is, even if the source code doesn't actually import them;
* edges that *are* in `content` but missing from this array are silently
* omitted. The lex+resolve fallback path has no such hole — its edges are
* always grounded in syntactic imports. Build-tool integrations that
* supply this field own the correctness of the graph they hand over.
*
* Type-only imports (`import type {...}`) are the most common asymmetry
* versus the lex+resolve path: the default lex (`es-module-lexer`) keeps
* them; pre-resolved callers backed by a Gro-style filer typically drop
* them. Both are intentional within their respective contracts.
*
* Cache semantics: the session compares this array element-wise (shallow
* equality) against the snapshot stored from the prior call — a fresh
* array with identical contents cache-hits, while any length, element, or
* order difference invalidates. Callers that produce fresh arrays per
* call (e.g., Gro's `[...filer.dependencies.keys()]`) reuse the cache
* cleanly across persistent-session calls.
*
* Order is significant. Reordering without a content change is treated
* as a real change — sort upstream if you want order-insensitive caching.
* Map-iteration-order callers (e.g., a Gro filer emitting
* `[...filer.dependencies.keys()]`) are naturally stable across calls for
* the same content, so no defensive sort is needed there.
*/
dependencies?: ReadonlyArray<string>;
}
/**
* Default analyzer resolver based on file extension.
*
* - `.svelte` → `'svelte'`
* - `.ts`, `.js` → `'typescript'`
* - `.css` → `'css'`
* - `.json` → `'json'`
* - Other extensions → `null` (skip)
*/
export const getDefaultAnalyzer = (path: string): AnalyzerType | null => {
if (isSvelte(path)) return 'svelte';
if (isTypescript(path)) return 'typescript';
if (isCss(path)) return 'css';
if (isJson(path)) return 'json';
return null;
};
/**
* Extract component name from a Svelte module path.
*
* @example
* ```ts
* getComponentName('Alert.svelte') // => 'Alert'
* getComponentName('components/Button.svelte') // => 'Button'
* ```
*/
export const getComponentName = (modulePath: string): string =>
modulePath.replace(/^.*\//, '').replace(/\.svelte$/, '');
/**
* Check if a path is a TypeScript or JS file.
*
* Includes both `.ts` and `.js` files since JS files are valid in TS projects.
* Excludes `.d.ts` declaration files — use a custom `getAnalyzerType` to include them.
*/
export const isTypescript = (path: string): boolean =>
(path.endsWith('.ts') && !path.endsWith('.d.ts')) || path.endsWith('.js');
/** Check if a path is a Svelte component file. */
export const isSvelte = (path: string): boolean => path.endsWith('.svelte');
/** Check if a path is a CSS file. */
export const isCss = (path: string): boolean => path.endsWith('.css');
/** Check if a path is a JSON file. */
export const isJson = (path: string): boolean => path.endsWith('.json');
/**
* Suffix appended to `.svelte` file paths to create virtual TypeScript file paths.
*
* Used by svelte2tsx integration: `Component.svelte` → `Component.svelte.__svelte2tsx__.ts`.
*/
export const SVELTE_VIRTUAL_SUFFIX = '.__svelte2tsx__.ts';
/**
* Strip the svelte2tsx virtual file suffix from a path, if present.
*
* Maps `Component.svelte.__svelte2tsx__.ts` back to `Component.svelte`.
* Returns the path unchanged if the suffix is not present.
*/
export const stripVirtualSuffix = (path: string): string =>
path.endsWith(SVELTE_VIRTUAL_SUFFIX) ? path.slice(0, -SVELTE_VIRTUAL_SUFFIX.length) : path;
{
"path": "source.ts",
"declarations": [
{
"name": "AnalyzerType",
"kind": "type",
"docComment": "Analyzer type for source files.\n\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",
"typeSignature": "AnalyzerType",
"sourceLine": 22,
"alsoExportedFrom": [
"index.ts"
]
},
{
"name": "SourceFileInfo",
"kind": "interface",
"docComment": "File information for source analysis.\n\nProvides file content to analysis functions from any source:\nfile system, build pipeline, or in-memory.\n\nNote: `content` is required to keep analysis functions pure (no hidden I/O).\nCallers are responsible for reading file content before analysis.",
"typeSignature": "SourceFileInfo",
"sourceLine": 33,
"alsoExportedFrom": [
"index.ts"
],
"members": [
{
"name": "id",
"kind": "variable",
"docComment": "Absolute path to the file.",
"typeSignature": "string"
},
{
"name": "content",
"kind": "variable",
"docComment": "File content (required - analysis functions don't read from disk).",
"typeSignature": "string"
},
{
"name": "dependencies",
"kind": "variable",
"docComment": "Pre-resolved absolute file paths of modules this file imports.\n\n**Opt-in optimization** — when supplied, the session treats this as the\nauthoritative dependency set for the file and skips its own lex+resolve\npass for this entry. Build-tool integrations (e.g., Gro's filer) that\nalready maintain a dependency graph can hand it over directly instead of\npaying the lex+resolve cost twice.\n\n**Omit (undefined) → default behavior** — the session lexes import\nspecifiers from `content` and resolves them via its `ImportResolver`.\nThis is the right choice when the caller doesn't already have a graph.\n\nOnly include resolved local imports — node_modules paths are filtered\nout at storage time by the configured `isSource` predicate either way.\n\n**Trust contract** — the session treats this array as authoritative and\ndoes not cross-check against the file's `content`. Edges declared here\nare accepted as-is, even if the source code doesn't actually import them;\nedges that *are* in `content` but missing from this array are silently\nomitted. The lex+resolve fallback path has no such hole — its edges are\nalways grounded in syntactic imports. Build-tool integrations that\nsupply this field own the correctness of the graph they hand over.\n\nType-only imports (`import type {...}`) are the most common asymmetry\nversus the lex+resolve path: the default lex (`es-module-lexer`) keeps\nthem; pre-resolved callers backed by a Gro-style filer typically drop\nthem. Both are intentional within their respective contracts.\n\nCache semantics: the session compares this array element-wise (shallow\nequality) against the snapshot stored from the prior call — a fresh\narray with identical contents cache-hits, while any length, element, or\norder difference invalidates. Callers that produce fresh arrays per\ncall (e.g., Gro's `[...filer.dependencies.keys()]`) reuse the cache\ncleanly across persistent-session calls.\n\nOrder is significant. Reordering without a content change is treated\nas a real change — sort upstream if you want order-insensitive caching.\nMap-iteration-order callers (e.g., a Gro filer emitting\n`[...filer.dependencies.keys()]`) are naturally stable across calls for\nthe same content, so no defensive sort is needed there.",
"typeSignature": "ReadonlyArray<string>",
"optional": true
}
]
},
{
"name": "getDefaultAnalyzer",
"kind": "function",
"docComment": "Default analyzer resolver based on file extension.\n\n- `.svelte` → `'svelte'`\n- `.ts`, `.js` → `'typescript'`\n- `.css` → `'css'`\n- `.json` → `'json'`\n- Other extensions → `null` (skip)",
"typeSignature": "(path: string): AnalyzerType | null",
"sourceLine": 92,
"parameters": [
{
"name": "path",
"type": "string"
}
],
"returnType": "AnalyzerType | null"
},
{
"name": "getComponentName",
"kind": "function",
"docComment": "Extract component name from a Svelte module path.",
"typeSignature": "(modulePath: string): string",
"sourceLine": 109,
"examples": [
"```ts\ngetComponentName('Alert.svelte') // => 'Alert'\ngetComponentName('components/Button.svelte') // => 'Button'\n```"
],
"parameters": [
{
"name": "modulePath",
"type": "string"
}
],
"returnType": "string"
},
{
"name": "isTypescript",
"kind": "function",
"docComment": "Check if a path is a TypeScript or JS file.\n\nIncludes both `.ts` and `.js` files since JS files are valid in TS projects.\nExcludes `.d.ts` declaration files — use a custom `getAnalyzerType` to include them.",
"typeSignature": "(path: string): boolean",
"sourceLine": 118,
"parameters": [
{
"name": "path",
"type": "string"
}
],
"returnType": "boolean"
},
{
"name": "isSvelte",
"kind": "function",
"docComment": "Check if a path is a Svelte component file.",
"typeSignature": "(path: string): boolean",
"sourceLine": 122,
"parameters": [
{
"name": "path",
"type": "string"
}
],
"returnType": "boolean"
},
{
"name": "isCss",
"kind": "function",
"docComment": "Check if a path is a CSS file.",
"typeSignature": "(path: string): boolean",
"sourceLine": 125,
"parameters": [
{
"name": "path",
"type": "string"
}
],
"returnType": "boolean"
},
{
"name": "isJson",
"kind": "function",
"docComment": "Check if a path is a JSON file.",
"typeSignature": "(path: string): boolean",
"sourceLine": 128,
"parameters": [
{
"name": "path",
"type": "string"
}
],
"returnType": "boolean"
},
{
"name": "SVELTE_VIRTUAL_SUFFIX",
"kind": "variable",
"docComment": "Suffix appended to `.svelte` file paths to create virtual TypeScript file paths.\n\nUsed by svelte2tsx integration: `Component.svelte` → `Component.svelte.__svelte2tsx__.ts`.",
"typeSignature": "\".__svelte2tsx__.ts\"",
"sourceLine": 135
},
{
"name": "stripVirtualSuffix",
"kind": "function",
"docComment": "Strip the svelte2tsx virtual file suffix from a path, if present.\n\nMaps `Component.svelte.__svelte2tsx__.ts` back to `Component.svelte`.\nReturns the path unchanged if the suffix is not present.",
"typeSignature": "(path: string): string",
"sourceLine": 143,
"parameters": [
{
"name": "path",
"type": "string"
}
],
"returnType": "string"
}
],
"moduleComment": "Source file type predicates and path helpers.\n\nPure functions for detecting file types by extension and extracting\ncomponent names from paths. No configuration dependency — these are\nthe building blocks used by `source-config.ts`.\n\n@see `source-config.ts` for configuration-aware functions (`isSource`, `extractPath`, etc.)\n@see `analyze.ts` for consumers (`analyze`, `analyzeFromFiles`)",
"dependents": [
"analyze-core.ts",
"analyze.ts",
"discovery.ts",
"exports.ts",
"files.ts",
"postprocess.ts",
"session.ts",
"source-config.ts",
"svelte.ts",
"typescript-exports.ts",
"typescript-program.ts",
"vite.ts"
]
}