feat(extraction): add Nix language support with module-system option wiring (#324, #332 via #648 — carries #1084) (#1190)

Carries @TyceHerrman's #1084 as the functional base. Extraction + file wiring (imports/modules lists, callPackage), module-system option-path synthesizer, lexical-scope resolution gates, ABI-15 wasm rebuilt from upstream source. Validated on agenix, nix-darwin, home-manager, and nixpkgs (44,368 files, 3m49s, 1.30M nodes).

Co-authored-by: Tyce Herrman <Tyce.Herrman@pm.me>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-06 12:41:32 -05:00
committed by GitHub
co-authored by Tyce Herrman Claude Fable 5
parent 99152212a9
commit 7f325134e0
17 changed files with 1262 additions and 7 deletions
+34
View File
@@ -40,8 +40,18 @@ const EXTENSION_RESOLUTION: Record<string, string[]> = {
php: ['.php'],
ruby: ['.rb'],
objc: ['.h', '.m', '.mm'],
nix: ['.nix', '/default.nix'],
};
export function isNixPathImportRef(ref: UnresolvedRef): boolean {
return (
ref.language === 'nix' &&
ref.referenceKind === 'imports' &&
(ref.referenceName.startsWith('./') || ref.referenceName.startsWith('../')) &&
!/[\s{}()[\];"'<>$]/.test(ref.referenceName)
);
}
/**
* Resolve an import path to an actual file
*/
@@ -1292,6 +1302,30 @@ export function resolveViaImport(
return null;
}
// Nix static project-path imports (`import ./x.nix`, `builtins.import ./dir`,
// `import ./x.nix {}`) resolve to file nodes only. Do not resolve
// angle-bracket channels, attribute expressions, variables, or other dynamic
// expressions as project files.
if (isNixPathImportRef(ref)) {
const resolvedPath = resolveImportPath(ref.referenceName, ref.filePath, ref.language, context);
if (!resolvedPath) return null;
const basename = resolvedPath.split('/').pop()!;
const fileNode = context
.getNodesByName(basename)
.find((n) => n.kind === 'file' && n.filePath === resolvedPath);
if (fileNode) {
return {
original: ref,
targetNodeId: fileNode.id,
confidence: 0.9,
resolvedBy: 'import',
};
}
return null;
}
// Use cached import mappings (avoids re-reading and re-parsing per ref)
const imports = context.getImportMappings(ref.filePath, ref.language);
if (imports.length === 0 && !context.readFile(ref.filePath)) {