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:
co-authored by
Tyce Herrman
Claude Fable 5
parent
99152212a9
commit
7f325134e0
+28
-3
@@ -17,7 +17,7 @@ import {
|
||||
ImportMapping,
|
||||
} from './types';
|
||||
import { matchReference, matchFunctionRef, matchDottedCallChain, matchScopedCallChain, sameLanguageFamily, crossesKnownFamily } from './name-matcher';
|
||||
import { resolveViaImport, resolveJvmImport, extractImportMappings, extractReExports, loadCppIncludeDirs, isPhpIncludePathRef, isCobolCopybookRef } from './import-resolver';
|
||||
import { resolveViaImport, resolveJvmImport, extractImportMappings, extractReExports, loadCppIncludeDirs, isPhpIncludePathRef, isCobolCopybookRef, isNixPathImportRef } from './import-resolver';
|
||||
import { detectFrameworks } from './frameworks';
|
||||
import { synthesizeCallbackEdges } from './callback-synthesizer';
|
||||
import { createYielder, type MaybeYield } from './cooperative-yield';
|
||||
@@ -747,11 +747,14 @@ export class ReferenceResolver {
|
||||
// ArkTS chained-attribute refs carry a leading dot (`.titleStyle`) that
|
||||
// routes them to the decorator-gated matcher; the symbol itself is
|
||||
// indexed under the bare name, so the existence check strips the dot.
|
||||
// Nix static path imports (`import ./x.nix`) name a FILE, not a symbol —
|
||||
// they bypass the symbol-existence check and resolve via resolveViaImport.
|
||||
const existenceName =
|
||||
ref.language === 'arkts' && ref.referenceName.startsWith('.')
|
||||
? ref.referenceName.slice(1)
|
||||
: ref.referenceName;
|
||||
if (
|
||||
!isNixPathImportRef(ref) &&
|
||||
!this.hasAnyPossibleMatch(existenceName) &&
|
||||
!this.matchesAnyImport(ref) &&
|
||||
!this.frameworks.some((f) => f.claimsReference?.(ref.referenceName))
|
||||
@@ -826,7 +829,9 @@ export class ReferenceResolver {
|
||||
// framework resolver IS the whole rulebook (`var.X` can never legally
|
||||
// bind outside its module directory), so the name-matcher's
|
||||
// qualified-name fallback would only ever add wrong cross-module edges.
|
||||
if (isPhpIncludePathRef(ref) || isCobolCopybookRef(ref) || ref.language === 'terraform') {
|
||||
// Nix static path imports are file references for the same reason —
|
||||
// falling through would let "./x.nix" name-match an unrelated node.
|
||||
if (isPhpIncludePathRef(ref) || isCobolCopybookRef(ref) || isNixPathImportRef(ref) || ref.language === 'terraform') {
|
||||
return candidates.length > 0
|
||||
? candidates.reduce((best, curr) =>
|
||||
curr.confidence > best.confidence ? curr : best
|
||||
@@ -835,7 +840,27 @@ export class ReferenceResolver {
|
||||
}
|
||||
|
||||
// Strategy 3: Try name matching
|
||||
const nameResult = this.gateLanguage(matchReference(ref, this.context), ref);
|
||||
let nameResult = this.gateLanguage(matchReference(ref, this.context), ref);
|
||||
// Nix has no ambient cross-file namespace — a callee binds lexically
|
||||
// (same file) or through explicit import/callPackage wiring (the import
|
||||
// path above). A cross-file name match is wrong by construction: every
|
||||
// module `inherit (lib) mkOption`s the same nixpkgs helpers, so the
|
||||
// matcher would link each `mkOption` call to whichever file's inherit
|
||||
// binding it happened to pick. Same-file matches only.
|
||||
if (nameResult) {
|
||||
const target = this.queries.getNodeById(nameResult.targetNodeId);
|
||||
if (ref.language === 'nix') {
|
||||
if (!target || target.filePath !== ref.filePath) {
|
||||
nameResult = null;
|
||||
}
|
||||
} else if (target && target.language === 'nix') {
|
||||
// The reverse direction is just as impossible: no other language can
|
||||
// symbolically call into a .nix binding (interop is eval/CLI, never a
|
||||
// linkable symbol) — without this, a Python script's `split()` lands
|
||||
// on some module's `split = ...` binding as a low-confidence match.
|
||||
nameResult = null;
|
||||
}
|
||||
}
|
||||
if (nameResult) {
|
||||
candidates.push(nameResult);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user