import { URI, type LangiumDocument } from 'langium'; import type { DomainLangServices } from '../domain-lang-module.js'; import type { LockFile } from './types.js'; /** * Resolution failure reason codes for programmatic handling. */ export type ImportResolutionReason = 'file-not-found' | 'unknown-alias' | 'missing-manifest' | 'not-installed' | 'dependency-not-found' | 'missing-entry' | 'unresolvable' | 'escapes-workspace'; /** * Structured error for import resolution failures. * * Carries the specifier, attempted paths, a reason code, and * a human-readable hint so callers can build precise diagnostics * without parsing error message strings. */ export declare class ImportResolutionError extends Error { /** The import specifier that failed to resolve. */ readonly specifier: string; /** Paths that were tried during resolution (in order). */ readonly attemptedPaths: readonly string[]; /** Machine-readable failure reason. */ readonly reason: ImportResolutionReason; /** Human-readable suggestion for fixing the problem. */ readonly hint: string; constructor(opts: { specifier: string; attemptedPaths?: string[]; reason: ImportResolutionReason; hint: string; message?: string; }); } /** * ImportResolver resolves import statements using manifest-centric rules (PRS-010). * * Import Types (PRS-010): * - Local relative: ./path, ../path → Directory-first resolution * - Path aliases: @/path, @alias/path → Configurable in model.yaml paths section * - External: dependency key → Manifest dependencies (key can be owner/package or an alias that maps to source) * * Directory-First Resolution: * - ./types → ./types/index.dlang → ./types.dlang * - Module entry defaults to index.dlang (no model.yaml required) * * Caching Strategy (PRS-017 R1 — uses Langium standard infrastructure): * - LSP mode: Uses `DocumentCache` keyed by importing document URI * Each document's import resolutions are cached independently. * When a document changes, only ITS cache entries are auto-cleared. * Cross-document invalidation (when an imported file moves/deletes) is * handled by DomainLangIndexManager calling `invalidateForDocuments()` * with the reverse dependency graph. * - Standalone mode: Uses `SimpleCache` - manual invalidation via clearCache() * * Why DocumentCache with manual cross-invalidation (not WorkspaceCache)? * - WorkspaceCache clears the ENTIRE cache on ANY document change * - In a 50-file workspace, editing one file caused ~50 redundant re-resolutions * - DocumentCache + targeted invalidation via reverse dep graph only clears * the changed file and its direct/transitive importers * - This matches gopls' per-package invalidation strategy * * @see https://langium.org/docs/recipes/caching/ for Langium caching patterns */ export declare class ImportResolver { private readonly workspaceManager; /** * Per-document cache for resolved import URIs. * In LSP mode: DocumentCache - clears only the changed document's entries. * Cross-document invalidation handled by DomainLangIndexManager. * In standalone mode: SimpleCache - manual invalidation via clearCache(). */ private readonly resolverCache; /** * Whether the cache is a DocumentCache (LSP mode) for targeted invalidation. */ private readonly isDocumentCache; /** * Cache for sorted alias entries to avoid re-sorting on every findMatchingAlias call. * Keyed by the alias object reference; invalidated when the reference changes. */ private cachedAliasSource; private cachedSortedAliases; /** * Creates an ImportResolver. * * @param services - DomainLang services. If `services.shared` is present, uses DocumentCache * for per-document invalidation. Otherwise uses SimpleCache for standalone mode. */ constructor(services: DomainLangServices); /** * Clears the entire import resolution cache. * Call explicitly when model.yaml or model.lock changes. */ clearCache(): void; /** * Invalidates cached import resolutions for specific documents (PRS-017 R1). * * Called by DomainLangIndexManager when files change, using the reverse * dependency graph to determine which documents' caches need clearing. * This provides targeted invalidation instead of clearing the entire cache. * * @param uris - Document URIs whose import resolution caches should be cleared */ invalidateForDocuments(uris: Iterable): void; /** * Resolve an import specifier relative to a Langium document. * Results are cached per-document using DocumentCache (PRS-017 R1). */ resolveForDocument(document: LangiumDocument, specifier: string): Promise; /** * Resolve an import specifier from a base directory (non-LSP contexts). */ resolveFrom(baseDir: string, specifier: string): Promise; /** * Resolves a path alias import. * * @param specifier - Import specifier starting with @ (e.g., "@/lib", "@shared/types") */ private resolvePathAlias; /** * Finds the longest matching alias for a specifier. */ private findMatchingAlias; /** * Resolves an external dependency via manifest. * * Import specifier is a dependency key from model.yaml. * - Recommended: key is owner/package. * - Optional: key is an alias with an explicit source. * The LSP only resolves to cached packages - no network calls. */ private resolveExternalDependency; /** * Resolves a local path using directory-first resolution. * * Per PRS-010 (updated design): * - If path ends with .dlang → direct file import * - If no extension → directory-first: * 1. Try ./path/index.dlang (module default, no model.yaml required) * 2. Try ./path.dlang (file fallback) */ private resolveLocalPath; /** * Directory-first resolution: ./types → ./types/index.dlang → ./types.dlang * * Module entry defaults to index.dlang without requiring model.yaml. * If the directory has model.yaml with custom entry, use that. */ private resolveDirectoryFirst; /** * Reads the entry point from a module's model.yaml. * Defaults to index.dlang if no manifest or no entry specified. */ private readModuleEntry; /** * Checks if a path is a directory. */ private isDirectory; /** * Get the current lock file (if loaded). */ getLockFile(): Promise; /** * Logs an import resolution trace message when `domainlang.lsp.traceImports` is enabled. * Output goes to stderr so it's visible in the LSP output channel. */ private trace; }