mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-14 23:22:54 +00:00
feat(languages): add Zig language provider
Wires Zig (.zig) into the ingestion pipeline as a first-class language.
Adds a tree-sitter-zig parser binding, language config, tree-sitter
queries, and dedicated extractor configs (class/method/call/field/type/
variable/import/named-binding).
Highlights of the Zig modeling:
- Container types (struct/enum/union) are bound via `variable_declaration`
in the Zig grammar; the class extractor walks the RHS to disambiguate
the label (Struct / Enum / Class — Union handling lands separately).
- @import("./path.zig") relative imports resolve against the importing
file; stdlib/builtin/root names are intentionally returned as null.
- Bare-name @import("pkg") for build.zig.zon dependencies is left as a
TODO (resolved in a follow-up commit).
- Top-level const/var declarations whose RHS is a struct/enum/union are
filtered out of the Const set so they don't double-emit alongside the
type node.
Validated end-to-end on FuryForged (~9,400 symbols, 700+ Zig functions):
function-level resolution, cross-file impact walks, and @import-driven
module edges all behave correctly.
Includes a fixture + integration test (test/integration/resolvers/
zig.test.ts) covering struct/enum detection, method extraction, and
relative-import resolution.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
247b1bd556
commit
ededebf012
25 changed files with 872 additions and 3 deletions
|
|
@ -42,6 +42,7 @@ const EXTENSION_MAP: Record<SupportedLanguages, readonly string[]> = {
|
|||
[SupportedLanguages.Swift]: ['.swift'],
|
||||
[SupportedLanguages.Dart]: ['.dart'],
|
||||
[SupportedLanguages.Vue]: ['.vue'],
|
||||
[SupportedLanguages.Zig]: ['.zig'],
|
||||
[SupportedLanguages.Cobol]: ['.cbl', '.cob', '.cpy', '.cobol'],
|
||||
} satisfies Record<SupportedLanguages, readonly string[]>; // Ensure exhaustiveness
|
||||
|
||||
|
|
@ -100,6 +101,7 @@ const SYNTAX_MAP: Record<SupportedLanguages, string> = {
|
|||
[SupportedLanguages.Swift]: 'swift',
|
||||
[SupportedLanguages.Dart]: 'dart',
|
||||
[SupportedLanguages.Vue]: 'typescript',
|
||||
[SupportedLanguages.Zig]: 'zig',
|
||||
[SupportedLanguages.Cobol]: 'cobol',
|
||||
} satisfies Record<SupportedLanguages, string>; // Ensure exhaustiveness
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export enum SupportedLanguages {
|
|||
Swift = 'swift',
|
||||
Dart = 'dart',
|
||||
Vue = 'vue',
|
||||
Zig = 'zig',
|
||||
/** Standalone regex processor — no tree-sitter, no LanguageProvider. */
|
||||
Cobol = 'cobol',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export const LanguageClassifications: Readonly<Record<SupportedLanguages, Langua
|
|||
[SupportedLanguages.Swift]: 'production',
|
||||
[SupportedLanguages.Dart]: 'production',
|
||||
[SupportedLanguages.Vue]: 'experimental',
|
||||
[SupportedLanguages.Zig]: 'experimental',
|
||||
[SupportedLanguages.Cobol]: 'experimental',
|
||||
};
|
||||
|
||||
|
|
|
|||
27
gitnexus/package-lock.json
generated
27
gitnexus/package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
|||
"@ladybugdb/core": "^0.15.2",
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@tree-sitter-grammars/tree-sitter-zig": "^1.1.2",
|
||||
"cli-progress": "^3.12.0",
|
||||
"commander": "^14.0.3",
|
||||
"cors": "^2.8.5",
|
||||
|
|
@ -55,6 +56,7 @@
|
|||
"@types/uuid": "^11.0.0",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"gitnexus-shared": "file:../gitnexus-shared",
|
||||
"graphology-types": "^0.24.8",
|
||||
"tsx": "^4.0.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^4.0.18"
|
||||
|
|
@ -75,7 +77,7 @@
|
|||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
"devDependencies": {
|
||||
"typescript": "^6.0.2"
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
|
|
@ -1917,6 +1919,25 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tree-sitter-grammars/tree-sitter-zig": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-zig/-/tree-sitter-zig-1.1.2.tgz",
|
||||
"integrity": "sha512-J0L31HZ2isy3F5zb2g5QWQOv2r/pbruQNL9ADhuQv2pn5BQOzxt80WcEJaYXBeuJ8GHxVT42slpCna8k1c8LOw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.0",
|
||||
"node-gyp-build": "^4.8.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.22.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree-sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
|
|
@ -3358,8 +3379,8 @@
|
|||
"version": "0.24.8",
|
||||
"resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.8.tgz",
|
||||
"integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/graphology-utils": {
|
||||
"version": "2.5.2",
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"@ladybugdb/core": "^0.15.2",
|
||||
"@modelcontextprotocol/sdk": "^1.0.0",
|
||||
"@scarf/scarf": "^1.4.0",
|
||||
"@tree-sitter-grammars/tree-sitter-zig": "^1.1.2",
|
||||
"cli-progress": "^3.12.0",
|
||||
"commander": "^14.0.3",
|
||||
"cors": "^2.8.5",
|
||||
|
|
@ -101,6 +102,7 @@
|
|||
"@types/uuid": "^11.0.0",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"gitnexus-shared": "file:../gitnexus-shared",
|
||||
"graphology-types": "^0.24.8",
|
||||
"tsx": "^4.0.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^4.0.18"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
// gitnexus/src/core/ingestion/call-extractors/configs/zig.ts
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { CallExtractionConfig } from '../../call-types.js';
|
||||
|
||||
export const zigCallConfig: CallExtractionConfig = {
|
||||
language: SupportedLanguages.Zig,
|
||||
};
|
||||
77
gitnexus/src/core/ingestion/class-extractors/configs/zig.ts
Normal file
77
gitnexus/src/core/ingestion/class-extractors/configs/zig.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// gitnexus/src/core/ingestion/class-extractors/configs/zig.ts
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
import type { ClassExtractionConfig, ClassLikeNodeLabel } from '../../class-types.js';
|
||||
|
||||
/**
|
||||
* Find the "type expression" child of a Zig variable_declaration.
|
||||
* Zig binds anonymous container types to variables:
|
||||
* const Pioneer = struct { ... };
|
||||
* const State = enum { ... };
|
||||
* const Tag = union(enum) { ... };
|
||||
* The container declaration is a child of the variable_declaration node,
|
||||
* not a top-level node like Rust's struct_item.
|
||||
*/
|
||||
function findContainerTypeChild(node: SyntaxNode): SyntaxNode | null {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (!child) continue;
|
||||
if (
|
||||
child.type === 'struct_declaration' ||
|
||||
child.type === 'enum_declaration' ||
|
||||
child.type === 'union_declaration'
|
||||
) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zig class extraction config.
|
||||
*
|
||||
* `variable_declaration` is the type-bearing node when its RHS is a
|
||||
* struct/enum/union. The variable's `identifier` provides the type name.
|
||||
*/
|
||||
export const zigClassConfig: ClassExtractionConfig = {
|
||||
language: SupportedLanguages.Zig,
|
||||
// Only variable_declaration is treated as a type declaration — the actual
|
||||
// shape (Struct/Enum/Class) is decided by extractType.
|
||||
typeDeclarationNodes: ['variable_declaration'],
|
||||
ancestorScopeNodeTypes: ['variable_declaration'],
|
||||
|
||||
extractName(node: SyntaxNode): string | undefined {
|
||||
if (node.type !== 'variable_declaration') return undefined;
|
||||
// Only emit a name if this variable_declaration actually wraps a container type.
|
||||
if (!findContainerTypeChild(node)) return undefined;
|
||||
// First named identifier child holds the bound name.
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const c = node.namedChild(i);
|
||||
if (c?.type === 'identifier') return c.text;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
extractType(node: SyntaxNode): ClassLikeNodeLabel | undefined {
|
||||
if (node.type !== 'variable_declaration') return undefined;
|
||||
const container = findContainerTypeChild(node);
|
||||
if (!container) return undefined;
|
||||
if (container.type === 'struct_declaration') return 'Struct';
|
||||
if (container.type === 'enum_declaration') return 'Enum';
|
||||
if (container.type === 'union_declaration') return 'Class';
|
||||
return undefined;
|
||||
},
|
||||
|
||||
// For nested containers, the enclosing variable_declaration's bound name
|
||||
// contributes the scope segment. We extract that identifier here.
|
||||
extractScopeSegments(scopeNode: SyntaxNode): string[] | undefined {
|
||||
if (scopeNode.type !== 'variable_declaration') return undefined;
|
||||
if (!findContainerTypeChild(scopeNode)) return [];
|
||||
for (let i = 0; i < scopeNode.namedChildCount; i++) {
|
||||
const c = scopeNode.namedChild(i);
|
||||
if (c?.type === 'identifier') return [c.text];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
|
@ -227,6 +227,10 @@ export const ENTRY_POINT_PATTERNS = {
|
|||
/^mapEventToState$/, // Legacy BLoC pattern
|
||||
],
|
||||
[SupportedLanguages.Vue]: [], // Vue uses TypeScript queries — entry points handled via TS patterns
|
||||
[SupportedLanguages.Zig]: [
|
||||
/^main$/, // standard executable entry point
|
||||
/^build$/, // build.zig entry point
|
||||
],
|
||||
[SupportedLanguages.Cobol]: [], // Standalone regex processor — no tree-sitter entry points
|
||||
} satisfies Record<SupportedLanguages, RegExp[]>;
|
||||
|
||||
|
|
|
|||
|
|
@ -246,3 +246,32 @@ export const rubyExportChecker: ExportChecker = (_node, _name) => true;
|
|||
|
||||
/** Dart: public if no leading underscore (convention, same as Python). */
|
||||
export const dartExportChecker: ExportChecker = (_node, name) => !name.startsWith('_');
|
||||
|
||||
/**
|
||||
* Zig: a definition is exported when the enclosing declaration carries the
|
||||
* `pub` keyword. `pub` is an anonymous (unnamed) child of the declaration
|
||||
* node — function_declaration, variable_declaration, etc.
|
||||
*/
|
||||
const ZIG_DECL_TYPES: ReadonlySet<string> = new Set([
|
||||
'function_declaration',
|
||||
'variable_declaration',
|
||||
'struct_declaration',
|
||||
'enum_declaration',
|
||||
'union_declaration',
|
||||
'container_field',
|
||||
]);
|
||||
|
||||
export const zigExportChecker: ExportChecker = (node, _name) => {
|
||||
let current: SyntaxNode | null = node;
|
||||
while (current) {
|
||||
if (ZIG_DECL_TYPES.has(current.type)) {
|
||||
for (let i = 0; i < current.childCount; i++) {
|
||||
const child = current.child(i);
|
||||
if (child && !child.isNamed && child.text === 'pub') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
|
|||
106
gitnexus/src/core/ingestion/field-extractors/zig.ts
Normal file
106
gitnexus/src/core/ingestion/field-extractors/zig.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// gitnexus/src/core/ingestion/field-extractors/zig.ts
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { BaseFieldExtractor } from '../field-extractor.js';
|
||||
import type {
|
||||
ExtractedFields,
|
||||
FieldExtractorContext,
|
||||
FieldInfo,
|
||||
FieldVisibility,
|
||||
} from '../field-types.js';
|
||||
import { extractSimpleTypeName } from '../type-extractors/shared.js';
|
||||
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
||||
|
||||
/**
|
||||
* Zig field extractor.
|
||||
*
|
||||
* Zig containers are anonymous values bound to a `variable_declaration`:
|
||||
* const Pioneer = struct { state: State, energy: u32 };
|
||||
* const State = enum { idle, working };
|
||||
*
|
||||
* The class extractor identifies a variable_declaration as a "type
|
||||
* declaration" when its RHS is struct_declaration / enum_declaration /
|
||||
* union_declaration. This field extractor mirrors that decision and
|
||||
* walks into the container to enumerate its `container_field` members.
|
||||
*
|
||||
* Visibility: Zig has no per-field modifier; container fields are part of
|
||||
* the type's public surface, so we report all as 'public'.
|
||||
*/
|
||||
export class ZigFieldExtractor extends BaseFieldExtractor {
|
||||
language = SupportedLanguages.Zig;
|
||||
|
||||
isTypeDeclaration(node: SyntaxNode): boolean {
|
||||
if (node.type !== 'variable_declaration') return false;
|
||||
return findContainerChild(node) !== null;
|
||||
}
|
||||
|
||||
protected extractVisibility(_node: SyntaxNode): FieldVisibility {
|
||||
return 'public';
|
||||
}
|
||||
|
||||
extract(node: SyntaxNode, context: FieldExtractorContext): ExtractedFields | null {
|
||||
if (!this.isTypeDeclaration(node)) return null;
|
||||
|
||||
// Owner name = the bound identifier, not a 'name' field.
|
||||
let ownerFqn: string | undefined;
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const c = node.namedChild(i);
|
||||
if (c?.type === 'identifier') {
|
||||
ownerFqn = c.text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ownerFqn) return null;
|
||||
|
||||
const container = findContainerChild(node);
|
||||
if (!container) return null;
|
||||
|
||||
const fields: FieldInfo[] = [];
|
||||
for (let i = 0; i < container.namedChildCount; i++) {
|
||||
const child = container.namedChild(i);
|
||||
if (child?.type !== 'container_field') continue;
|
||||
const field = this.buildField(child, context);
|
||||
if (field) fields.push(field);
|
||||
}
|
||||
|
||||
return { ownerFqn, fields, nestedTypes: [] };
|
||||
}
|
||||
|
||||
private buildField(node: SyntaxNode, context: FieldExtractorContext): FieldInfo | null {
|
||||
const nameNode = node.childForFieldName?.('name');
|
||||
const name = nameNode?.text;
|
||||
if (!name) return null;
|
||||
|
||||
const typeNode = node.childForFieldName?.('type');
|
||||
const rawType = typeNode
|
||||
? (extractSimpleTypeName(typeNode) ?? typeNode.text?.trim() ?? null)
|
||||
: null;
|
||||
const type = this.normalizeType(rawType);
|
||||
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
visibility: 'public',
|
||||
isStatic: false,
|
||||
isReadonly: false,
|
||||
sourceFile: context.filePath,
|
||||
line: node.startPosition.row + 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function findContainerChild(node: SyntaxNode): SyntaxNode | null {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const c = node.namedChild(i);
|
||||
if (
|
||||
c?.type === 'struct_declaration' ||
|
||||
c?.type === 'enum_declaration' ||
|
||||
c?.type === 'union_declaration'
|
||||
) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const zigFieldExtractor = new ZigFieldExtractor();
|
||||
|
|
@ -896,6 +896,7 @@ export const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
|
|||
},
|
||||
],
|
||||
[SupportedLanguages.Vue]: [], // Vue uses TypeScript AST framework detection
|
||||
[SupportedLanguages.Zig]: [], // No mainstream Zig frameworks tracked yet
|
||||
[SupportedLanguages.Cobol]: [], // Standalone regex processor — no AST framework patterns
|
||||
} satisfies Record<SupportedLanguages, AstFrameworkPatternConfig[]>;
|
||||
|
||||
|
|
|
|||
19
gitnexus/src/core/ingestion/import-resolvers/configs/zig.ts
Normal file
19
gitnexus/src/core/ingestion/import-resolvers/configs/zig.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Zig import resolution config.
|
||||
* Per-file @import("...") strings, then standard fallback.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js';
|
||||
import { createStandardStrategy } from '../standard.js';
|
||||
import { resolveZigImportInternal } from '../zig.js';
|
||||
|
||||
export const zigModuleStrategy: ImportResolverStrategy = (rawImportPath, filePath, ctx) => {
|
||||
const resolved = resolveZigImportInternal(filePath, rawImportPath, ctx.allFilePaths);
|
||||
return resolved ? { kind: 'files', files: [resolved] } : null;
|
||||
};
|
||||
|
||||
export const zigImportConfig: ImportResolutionConfig = {
|
||||
language: SupportedLanguages.Zig,
|
||||
strategies: [zigModuleStrategy, createStandardStrategy(SupportedLanguages.Zig)],
|
||||
};
|
||||
59
gitnexus/src/core/ingestion/import-resolvers/zig.ts
Normal file
59
gitnexus/src/core/ingestion/import-resolvers/zig.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Zig module import resolution — internal helpers.
|
||||
*
|
||||
* Zig imports take three shapes:
|
||||
* const std = @import("std"); → stdlib, unresolvable
|
||||
* const builtin = @import("builtin"); → compiler builtin, unresolvable
|
||||
* const root = @import("root"); → user's main module, unresolvable here
|
||||
* const foo = @import("./foo.zig"); → relative path
|
||||
* const foo = @import("foo.zig"); → also relative (Zig treats unprefixed
|
||||
* paths with a `.zig` extension as
|
||||
* filesystem-relative to the importer)
|
||||
* const bar = @import("bar"); → package dep declared in build.zig.zon
|
||||
* (TODO: resolve via build.zig.zon)
|
||||
*
|
||||
* Only the relative-path cases are resolved here. Stdlib / builtin / root
|
||||
* names and unrecognised package names return null so the standard fallback
|
||||
* can attempt suffix matching.
|
||||
*/
|
||||
|
||||
const ZIG_STDLIB_NAMES = new Set(['std', 'builtin', 'root']);
|
||||
|
||||
/** Resolve a Zig @import argument to a file path in the repository.
|
||||
* Returns null when the import is a stdlib / builtin / root reference,
|
||||
* a build.zig.zon package dep, or genuinely unresolvable. */
|
||||
export function resolveZigImportInternal(
|
||||
currentFile: string,
|
||||
importPath: string,
|
||||
allFiles: Set<string>,
|
||||
): string | null {
|
||||
// Stdlib / compiler builtin / root — not resolvable from source files alone.
|
||||
if (ZIG_STDLIB_NAMES.has(importPath)) return null;
|
||||
|
||||
// Strip any explicit `.zig` extension for path arithmetic; we re-add it below.
|
||||
const trimmed = importPath.replace(/\\/g, '/');
|
||||
|
||||
// Path-bearing import: resolve relative to the current file's directory.
|
||||
// Zig allows both "./foo.zig" and "foo.zig" — both are filesystem-relative.
|
||||
if (trimmed.endsWith('.zig') || trimmed.includes('/')) {
|
||||
const currentDir = currentFile.split('/').slice(0, -1);
|
||||
const parts = trimmed.split('/');
|
||||
for (const part of parts) {
|
||||
if (part === '' || part === '.') continue;
|
||||
if (part === '..') {
|
||||
currentDir.pop();
|
||||
} else {
|
||||
currentDir.push(part);
|
||||
}
|
||||
}
|
||||
const candidate = currentDir.join('/');
|
||||
if (allFiles.has(candidate)) return candidate;
|
||||
if (allFiles.has(candidate + '.zig')) return candidate + '.zig';
|
||||
return null;
|
||||
}
|
||||
|
||||
// Bare name without extension or slashes (e.g. @import("bar")).
|
||||
// TODO: resolve via build.zig.zon package map. For now, return null and
|
||||
// let the standard suffix matcher try its luck.
|
||||
return null;
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import { rubyProvider } from './ruby.js';
|
|||
import { swiftProvider } from './swift.js';
|
||||
import { dartProvider } from './dart.js';
|
||||
import { vueProvider } from './vue.js';
|
||||
import { zigProvider } from './zig.js';
|
||||
import { cobolProvider } from './cobol.js';
|
||||
|
||||
export const providers = {
|
||||
|
|
@ -42,6 +43,7 @@ export const providers = {
|
|||
[SupportedLanguages.Swift]: swiftProvider,
|
||||
[SupportedLanguages.Dart]: dartProvider,
|
||||
[SupportedLanguages.Vue]: vueProvider,
|
||||
[SupportedLanguages.Zig]: zigProvider,
|
||||
[SupportedLanguages.Cobol]: cobolProvider,
|
||||
} satisfies Record<SupportedLanguages, LanguageProvider>;
|
||||
|
||||
|
|
|
|||
99
gitnexus/src/core/ingestion/languages/zig.ts
Normal file
99
gitnexus/src/core/ingestion/languages/zig.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Zig Language Provider
|
||||
*
|
||||
* Mirrors the Rust provider — Zig is the closest analog (systems language,
|
||||
* per-symbol named imports via `const X = @import("...")`, no inheritance,
|
||||
* no MRO).
|
||||
*
|
||||
* Key Zig traits:
|
||||
* - importSemantics: 'named' (each `@import` binds to one local name)
|
||||
* - mroStrategy: 'first-wins' (no inheritance, MRO is irrelevant)
|
||||
* - namedBindingExtractor: returns the local-name → exported-name pair
|
||||
* for the enclosing variable_declaration of the @import call.
|
||||
*
|
||||
* Container types (struct / enum / union) are anonymous values bound to
|
||||
* a variable_declaration. The class extractor disambiguates these from
|
||||
* ordinary variable declarations by inspecting the RHS.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { createCallExtractor } from '../call-extractors/generic.js';
|
||||
import { zigCallConfig } from '../call-extractors/configs/zig.js';
|
||||
import { createClassExtractor } from '../class-extractors/generic.js';
|
||||
import { zigClassConfig } from '../class-extractors/configs/zig.js';
|
||||
import { zigExportChecker } from '../export-detection.js';
|
||||
import { zigFieldExtractor } from '../field-extractors/zig.js';
|
||||
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
|
||||
import { zigImportConfig } from '../import-resolvers/configs/zig.js';
|
||||
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
||||
import { defineLanguage } from '../language-provider.js';
|
||||
import { createMethodExtractor } from '../method-extractors/generic.js';
|
||||
import { zigMethodConfig } from '../method-extractors/configs/zig.js';
|
||||
import { extractZigNamedBindings } from '../named-bindings/zig.js';
|
||||
import { ZIG_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { typeConfig as zigConfig } from '../type-extractors/zig.js';
|
||||
import { createVariableExtractor } from '../variable-extractors/generic.js';
|
||||
import { zigVariableConfig } from '../variable-extractors/configs/zig.js';
|
||||
|
||||
// Zig builtins that should never be treated as user-defined call targets.
|
||||
// All `@`-prefixed names plus a handful of conventionally noisy stdlib helpers.
|
||||
const BUILT_INS: ReadonlySet<string> = new Set([
|
||||
// Core builtins (compile-time intrinsics)
|
||||
'@import',
|
||||
'@intCast',
|
||||
'@as',
|
||||
'@ptrCast',
|
||||
'@sizeOf',
|
||||
'@alignOf',
|
||||
'@TypeOf',
|
||||
'@typeInfo',
|
||||
'@typeName',
|
||||
'@field',
|
||||
'@hasField',
|
||||
'@hasDecl',
|
||||
'@compileError',
|
||||
'@compileLog',
|
||||
'@panic',
|
||||
'@truncate',
|
||||
'@bitCast',
|
||||
'@floatCast',
|
||||
'@floatFromInt',
|
||||
'@intFromFloat',
|
||||
'@intFromBool',
|
||||
'@boolFromInt',
|
||||
'@enumFromInt',
|
||||
'@intFromEnum',
|
||||
'@errorName',
|
||||
'@embedFile',
|
||||
'@max',
|
||||
'@min',
|
||||
'@memcpy',
|
||||
'@memset',
|
||||
'@addWithOverflow',
|
||||
'@subWithOverflow',
|
||||
'@mulWithOverflow',
|
||||
'@shlWithOverflow',
|
||||
// Common stdlib helpers that would otherwise overwhelm the call graph.
|
||||
'panic',
|
||||
'assert',
|
||||
'print',
|
||||
'debugPrint',
|
||||
]);
|
||||
|
||||
export const zigProvider = defineLanguage({
|
||||
id: SupportedLanguages.Zig,
|
||||
extensions: ['.zig'],
|
||||
treeSitterQueries: ZIG_QUERIES,
|
||||
typeConfig: zigConfig,
|
||||
exportChecker: zigExportChecker,
|
||||
importResolver: createImportResolver(zigImportConfig),
|
||||
namedBindingExtractor: extractZigNamedBindings,
|
||||
// 'first-wins' is the default; Zig has no inheritance so MRO is irrelevant.
|
||||
callExtractor: createCallExtractor(zigCallConfig),
|
||||
fieldExtractor: zigFieldExtractor,
|
||||
methodExtractor: createMethodExtractor(zigMethodConfig),
|
||||
variableExtractor: createVariableExtractor(zigVariableConfig),
|
||||
classExtractor: createClassExtractor(zigClassConfig),
|
||||
heritageExtractor: createHeritageExtractor(SupportedLanguages.Zig),
|
||||
builtInNames: BUILT_INS,
|
||||
});
|
||||
138
gitnexus/src/core/ingestion/method-extractors/configs/zig.ts
Normal file
138
gitnexus/src/core/ingestion/method-extractors/configs/zig.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// gitnexus/src/core/ingestion/method-extractors/configs/zig.ts
|
||||
// Verified against @tree-sitter-grammars/tree-sitter-zig 1.1.2
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type {
|
||||
MethodExtractionConfig,
|
||||
MethodVisibility,
|
||||
ParameterInfo,
|
||||
} from '../../method-types.js';
|
||||
import { extractSimpleTypeName } from '../../type-extractors/shared.js';
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
|
||||
// Anonymous keyword children of a function_declaration node.
|
||||
function hasKeywordChild(node: SyntaxNode, keyword: string): boolean {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const c = node.child(i);
|
||||
if (c && !c.isNamed && c.text === keyword) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractZigMethodName(node: SyntaxNode): string | undefined {
|
||||
const nameNode = node.childForFieldName?.('name');
|
||||
return nameNode?.text;
|
||||
}
|
||||
|
||||
function extractZigReturnType(node: SyntaxNode): string | undefined {
|
||||
const typeNode = node.childForFieldName?.('type');
|
||||
if (!typeNode) return undefined;
|
||||
return extractSimpleTypeName(typeNode) ?? typeNode.text?.trim();
|
||||
}
|
||||
|
||||
function extractZigParameters(node: SyntaxNode): ParameterInfo[] {
|
||||
const paramList = node.childForFieldName?.('parameters');
|
||||
if (!paramList) return [];
|
||||
const params: ParameterInfo[] = [];
|
||||
for (let i = 0; i < paramList.namedChildCount; i++) {
|
||||
const param = paramList.namedChild(i);
|
||||
if (!param || param.type !== 'parameter') continue;
|
||||
const nameNode = param.childForFieldName?.('name');
|
||||
const typeNode = param.childForFieldName?.('type');
|
||||
params.push({
|
||||
name: nameNode?.text ?? '?',
|
||||
type: typeNode ? (extractSimpleTypeName(typeNode) ?? typeNode.text?.trim() ?? null) : null,
|
||||
rawType: typeNode?.text?.trim() ?? null,
|
||||
isOptional: false,
|
||||
isVariadic: false,
|
||||
});
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function extractZigVisibility(node: SyntaxNode): MethodVisibility {
|
||||
return hasKeywordChild(node, 'pub') ? 'public' : 'private';
|
||||
}
|
||||
|
||||
/** A Zig "method" is detected as a function_declaration whose first
|
||||
* parameter is named `self` — purely conventional, the language has no
|
||||
* receiver syntax. We expose the convention via extractReceiverType so
|
||||
* the call-resolution pipeline can route receiver.method() calls. */
|
||||
function extractZigReceiverType(node: SyntaxNode): string | undefined {
|
||||
const paramList = node.childForFieldName?.('parameters');
|
||||
if (!paramList) return undefined;
|
||||
const first = paramList.namedChild(0);
|
||||
if (!first || first.type !== 'parameter') return undefined;
|
||||
const nameNode = first.childForFieldName?.('name');
|
||||
if (nameNode?.text !== 'self') return undefined;
|
||||
const typeNode = first.childForFieldName?.('type');
|
||||
return typeNode?.text?.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner resolution for a function_declaration nested inside a container.
|
||||
* The container is a struct_declaration / enum_declaration / union_declaration,
|
||||
* which is itself the value child of a variable_declaration. The owner name
|
||||
* is the bound identifier on that variable_declaration.
|
||||
*/
|
||||
function extractZigOwnerName(node: SyntaxNode): string | undefined {
|
||||
if (
|
||||
node.type !== 'struct_declaration' &&
|
||||
node.type !== 'enum_declaration' &&
|
||||
node.type !== 'union_declaration'
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
const parent = node.parent;
|
||||
if (parent?.type !== 'variable_declaration') return undefined;
|
||||
for (let i = 0; i < parent.namedChildCount; i++) {
|
||||
const c = parent.namedChild(i);
|
||||
if (c?.type === 'identifier') return c.text;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Zig config
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const zigMethodConfig: MethodExtractionConfig = {
|
||||
language: SupportedLanguages.Zig,
|
||||
// Methods live inside container declarations (struct/enum/union).
|
||||
typeDeclarationNodes: ['struct_declaration', 'enum_declaration', 'union_declaration'],
|
||||
methodNodeTypes: ['function_declaration'],
|
||||
// The container declaration itself is the body — function_declaration nodes
|
||||
// are direct named children.
|
||||
bodyNodeTypes: ['struct_declaration', 'enum_declaration', 'union_declaration'],
|
||||
|
||||
extractOwnerName: extractZigOwnerName,
|
||||
extractName: extractZigMethodName,
|
||||
extractReturnType: extractZigReturnType,
|
||||
extractParameters: extractZigParameters,
|
||||
extractVisibility: extractZigVisibility,
|
||||
|
||||
isStatic(node: SyntaxNode): boolean {
|
||||
// Static = no `self` first parameter.
|
||||
const paramList = node.childForFieldName?.('parameters');
|
||||
if (!paramList) return true;
|
||||
const first = paramList.namedChild(0);
|
||||
if (!first || first.type !== 'parameter') return true;
|
||||
const nameNode = first.childForFieldName?.('name');
|
||||
return nameNode?.text !== 'self';
|
||||
},
|
||||
|
||||
isAbstract(_node: SyntaxNode, _ownerNode: SyntaxNode): boolean {
|
||||
// Zig has no abstract method concept.
|
||||
return false;
|
||||
},
|
||||
|
||||
isFinal(): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
extractReceiverType: extractZigReceiverType,
|
||||
|
||||
extractAnnotations(_node: SyntaxNode): string[] {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
36
gitnexus/src/core/ingestion/named-bindings/zig.ts
Normal file
36
gitnexus/src/core/ingestion/named-bindings/zig.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
||||
import type { NamedBinding } from './types.js';
|
||||
|
||||
/**
|
||||
* Zig named-binding extraction.
|
||||
*
|
||||
* The capture in tree-sitter-queries.ts pins `@import` as the import node
|
||||
* (the builtin_function call). The enclosing variable_declaration gives us
|
||||
* the local name:
|
||||
*
|
||||
* const std = @import("std"); → local "std", exported = "std"
|
||||
* const ArrayList = std.ArrayList; → field access alias chain (local
|
||||
* "ArrayList", exported "ArrayList")
|
||||
*
|
||||
* We treat the bound variable name as a per-symbol binding — this matches
|
||||
* `importSemantics: 'named'` and ensures cross-file references through
|
||||
* an aliased identifier resolve to the right module.
|
||||
*/
|
||||
export function extractZigNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined {
|
||||
// The query captures the builtin_function (@import call). Walk up to the
|
||||
// enclosing variable_declaration to find the bound local name.
|
||||
let current: SyntaxNode | null = importNode;
|
||||
while (current && current.type !== 'variable_declaration') {
|
||||
current = current.parent;
|
||||
}
|
||||
if (!current) return undefined;
|
||||
|
||||
// First named identifier child is the bound name.
|
||||
for (let i = 0; i < current.namedChildCount; i++) {
|
||||
const c = current.namedChild(i);
|
||||
if (c?.type === 'identifier') {
|
||||
return [{ local: c.text, exported: c.text }];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -1332,6 +1332,66 @@ export const DART_QUERIES = `
|
|||
(type_identifier) @heritage.trait))) @heritage
|
||||
`;
|
||||
|
||||
// Zig queries — works with @tree-sitter-grammars/tree-sitter-zig.
|
||||
// Zig has no top-level struct/enum/union node types; instead these are anonymous
|
||||
// values bound to a `variable_declaration`. We capture the variable_declaration
|
||||
// as the type-bearing node and let the class extractor disambiguate.
|
||||
//
|
||||
// Top-level const/var queries use a #not-match? predicate to exclude
|
||||
// declarations whose RHS is a struct/enum/union — those would otherwise
|
||||
// double-emit alongside @definition.struct/@definition.enum/@definition.class.
|
||||
export const ZIG_QUERIES = `
|
||||
; ── Functions (top-level and FFI) ────────────────────────────────────────────
|
||||
(source_file
|
||||
(function_declaration name: (identifier) @name) @definition.function)
|
||||
|
||||
; ── Container types bound to a variable_declaration ──────────────────────────
|
||||
; pub const Pioneer = struct { ... };
|
||||
(variable_declaration
|
||||
(identifier) @name
|
||||
(struct_declaration)) @definition.struct
|
||||
; pub const State = enum { ... };
|
||||
(variable_declaration
|
||||
(identifier) @name
|
||||
(enum_declaration)) @definition.enum
|
||||
; const Tag = union(enum) { ... }; — labelled as Class (no Union NodeLabel)
|
||||
(variable_declaration
|
||||
(identifier) @name
|
||||
(union_declaration)) @definition.class
|
||||
|
||||
; ── Methods inside container bodies ──────────────────────────────────────────
|
||||
(struct_declaration
|
||||
(function_declaration name: (identifier) @name) @definition.method)
|
||||
(enum_declaration
|
||||
(function_declaration name: (identifier) @name) @definition.method)
|
||||
(union_declaration
|
||||
(function_declaration name: (identifier) @name) @definition.method)
|
||||
|
||||
; ── Container fields (struct fields, enum members, union variants) ───────────
|
||||
(struct_declaration
|
||||
(container_field name: (identifier) @name) @definition.property)
|
||||
(union_declaration
|
||||
(container_field name: (identifier) @name) @definition.property)
|
||||
(enum_declaration
|
||||
(container_field name: (identifier) @name) @definition.property)
|
||||
|
||||
; ── Imports: @import("path") builtin ─────────────────────────────────────────
|
||||
(builtin_function
|
||||
(builtin_identifier) @_b
|
||||
(arguments (string (string_content) @import.source))
|
||||
(#eq? @_b "@import")) @import
|
||||
|
||||
; ── Calls ─────────────────────────────────────────────────────────────────────
|
||||
(call_expression function: (identifier) @call.name) @call
|
||||
(call_expression
|
||||
function: (field_expression member: (identifier) @call.name)) @call
|
||||
|
||||
; ── Top-level const/var (excluding type declarations) ────────────────────────
|
||||
(source_file
|
||||
(variable_declaration (identifier) @name) @definition.const
|
||||
(#not-match? @definition.const "(struct|enum|union)\\\\s*[({]"))
|
||||
`;
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
|
||||
export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
||||
|
|
@ -1350,5 +1410,6 @@ export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
|||
[SupportedLanguages.Swift]: SWIFT_QUERIES,
|
||||
[SupportedLanguages.Dart]: DART_QUERIES,
|
||||
[SupportedLanguages.Vue]: TYPESCRIPT_QUERIES, // Vue <script> blocks are parsed as TypeScript
|
||||
[SupportedLanguages.Zig]: ZIG_QUERIES,
|
||||
[SupportedLanguages.Cobol]: '', // Standalone regex processor — no tree-sitter queries
|
||||
};
|
||||
|
|
|
|||
61
gitnexus/src/core/ingestion/type-extractors/zig.ts
Normal file
61
gitnexus/src/core/ingestion/type-extractors/zig.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// gitnexus/src/core/ingestion/type-extractors/zig.ts
|
||||
//
|
||||
// Zig type-environment extraction.
|
||||
//
|
||||
// Zig is a typed language, but type inference is performed by the
|
||||
// compiler at compile time and is not always reflected in the surface
|
||||
// syntax. For call-graph purposes we extract only the high-confidence
|
||||
// patterns — explicit type annotations and parameter types. Anything
|
||||
// that requires type inference (e.g., `const x = makeFoo()`) is left
|
||||
// as an unresolved binding for now.
|
||||
|
||||
import type {
|
||||
LanguageTypeConfig,
|
||||
ParameterExtractor,
|
||||
TypeBindingExtractor,
|
||||
} from './types.js';
|
||||
import { extractSimpleTypeName } from './shared.js';
|
||||
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
||||
|
||||
const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set(['variable_declaration']);
|
||||
|
||||
/** Zig: const x: Foo = ... | var x: Foo = ... */
|
||||
const extractDeclaration: TypeBindingExtractor = (
|
||||
node: SyntaxNode,
|
||||
env: Map<string, string>,
|
||||
): void => {
|
||||
if (node.type !== 'variable_declaration') return;
|
||||
// Find the bound identifier (first named identifier child).
|
||||
let nameText: string | undefined;
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const c = node.namedChild(i);
|
||||
if (c?.type === 'identifier') {
|
||||
nameText = c.text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nameText) return;
|
||||
const typeNode = node.childForFieldName?.('type');
|
||||
if (!typeNode) return;
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
if (typeName) env.set(nameText, typeName);
|
||||
};
|
||||
|
||||
/** Zig: parameter → name + type */
|
||||
const extractParameter: ParameterExtractor = (
|
||||
node: SyntaxNode,
|
||||
env: Map<string, string>,
|
||||
): void => {
|
||||
if (node.type !== 'parameter') return;
|
||||
const nameNode = node.childForFieldName?.('name');
|
||||
const typeNode = node.childForFieldName?.('type');
|
||||
if (!nameNode || !typeNode) return;
|
||||
const typeName = extractSimpleTypeName(typeNode);
|
||||
if (typeName) env.set(nameNode.text, typeName);
|
||||
};
|
||||
|
||||
export const typeConfig: LanguageTypeConfig = {
|
||||
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
||||
extractDeclaration,
|
||||
extractParameter,
|
||||
};
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// gitnexus/src/core/ingestion/variable-extractors/configs/zig.ts
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { VariableExtractionConfig, VariableVisibility } from '../../variable-types.js';
|
||||
import { extractSimpleTypeName } from '../../type-extractors/shared.js';
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
|
||||
/**
|
||||
* Zig variable extraction config.
|
||||
*
|
||||
* Zig has a single `variable_declaration` node type that covers both
|
||||
* `const` and `var`. The const/var distinction is an anonymous keyword
|
||||
* child of the declaration. There is no separate `static` concept at
|
||||
* the declaration level (statics are achieved via comptime initialization).
|
||||
*
|
||||
* Type-bearing declarations whose RHS is struct/enum/union are treated
|
||||
* as type declarations by the class extractor, so they should not also
|
||||
* surface as ordinary variables. The query in tree-sitter-queries.ts
|
||||
* already filters those out with a #not-match? predicate.
|
||||
*/
|
||||
|
||||
function hasKeyword(node: SyntaxNode, keyword: string): boolean {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const c = node.child(i);
|
||||
if (c && !c.isNamed && c.text === keyword) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const zigVariableConfig: VariableExtractionConfig = {
|
||||
language: SupportedLanguages.Zig,
|
||||
// Zig's variable_declaration covers both const and var; the factory uses
|
||||
// these sets primarily for routing — overlap is fine because isConst() is
|
||||
// the source of truth at extraction time.
|
||||
constNodeTypes: ['variable_declaration'],
|
||||
staticNodeTypes: [],
|
||||
variableNodeTypes: ['variable_declaration'],
|
||||
|
||||
extractName(node: SyntaxNode): string | undefined {
|
||||
if (node.type !== 'variable_declaration') return undefined;
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const c = node.namedChild(i);
|
||||
if (c?.type === 'identifier') return c.text;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
extractType(node: SyntaxNode): string | undefined {
|
||||
const typeNode = node.childForFieldName?.('type');
|
||||
if (typeNode) return extractSimpleTypeName(typeNode) ?? typeNode.text?.trim();
|
||||
return undefined;
|
||||
},
|
||||
|
||||
extractVisibility(node: SyntaxNode): VariableVisibility {
|
||||
return hasKeyword(node, 'pub') ? 'public' : 'private';
|
||||
},
|
||||
|
||||
isConst(node: SyntaxNode): boolean {
|
||||
return hasKeyword(node, 'const');
|
||||
},
|
||||
|
||||
isStatic(_node: SyntaxNode): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
isMutable(node: SyntaxNode): boolean {
|
||||
return hasKeyword(node, 'var');
|
||||
},
|
||||
};
|
||||
|
|
@ -10,6 +10,7 @@ import CPP from 'tree-sitter-cpp';
|
|||
import CSharp from 'tree-sitter-c-sharp/bindings/node/index.js';
|
||||
import Go from 'tree-sitter-go';
|
||||
import Rust from 'tree-sitter-rust';
|
||||
import Zig from '@tree-sitter-grammars/tree-sitter-zig';
|
||||
import PHP from 'tree-sitter-php';
|
||||
import Ruby from 'tree-sitter-ruby';
|
||||
import { createRequire } from 'node:module';
|
||||
|
|
@ -314,6 +315,7 @@ const languageMap: Record<string, TreeSitterLanguage> = {
|
|||
...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
|
||||
[SupportedLanguages.PHP]: PHP.php_only,
|
||||
[SupportedLanguages.Ruby]: Ruby,
|
||||
[SupportedLanguages.Zig]: Zig,
|
||||
[SupportedLanguages.Vue]: TypeScript.typescript,
|
||||
...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
|
||||
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import Go from 'tree-sitter-go';
|
|||
import Rust from 'tree-sitter-rust';
|
||||
import PHP from 'tree-sitter-php';
|
||||
import Ruby from 'tree-sitter-ruby';
|
||||
import Zig from '@tree-sitter-grammars/tree-sitter-zig';
|
||||
import { createRequire } from 'node:module';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ const languageMap: Record<string, any> = {
|
|||
...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
|
||||
[SupportedLanguages.PHP]: PHP.php_only,
|
||||
[SupportedLanguages.Ruby]: Ruby,
|
||||
[SupportedLanguages.Zig]: Zig,
|
||||
[SupportedLanguages.Vue]: TypeScript.typescript,
|
||||
...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
|
||||
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
|
||||
|
|
|
|||
12
gitnexus/test/fixtures/lang-resolution/zig-basic/src/main.zig
vendored
Normal file
12
gitnexus/test/fixtures/lang-resolution/zig-basic/src/main.zig
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const std = @import("std");
|
||||
const pioneer = @import("./pioneer.zig");
|
||||
|
||||
pub fn main() void {
|
||||
var p = pioneer.Pioneer{ .energy = 0 };
|
||||
p.tick();
|
||||
helper();
|
||||
}
|
||||
|
||||
fn helper() void {
|
||||
_ = 1;
|
||||
}
|
||||
13
gitnexus/test/fixtures/lang-resolution/zig-basic/src/pioneer.zig
vendored
Normal file
13
gitnexus/test/fixtures/lang-resolution/zig-basic/src/pioneer.zig
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
pub const State = enum { idle, working };
|
||||
|
||||
pub const Pioneer = struct {
|
||||
energy: u32,
|
||||
|
||||
pub fn tick(self: *Pioneer) void {
|
||||
self.energy += 1;
|
||||
}
|
||||
|
||||
pub fn reset(self: *Pioneer) void {
|
||||
self.energy = 0;
|
||||
}
|
||||
};
|
||||
44
gitnexus/test/integration/resolvers/zig.test.ts
Normal file
44
gitnexus/test/integration/resolvers/zig.test.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* Zig: container types, methods, calls, and @import resolution.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import path from 'path';
|
||||
import {
|
||||
FIXTURES,
|
||||
getNodesByLabel,
|
||||
getRelationships,
|
||||
runPipelineFromRepo,
|
||||
type PipelineResult,
|
||||
} from './helpers.js';
|
||||
|
||||
describe('Zig basic resolution', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'zig-basic'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('detects the Pioneer struct and State enum', () => {
|
||||
expect(getNodesByLabel(result, 'Struct')).toContain('Pioneer');
|
||||
expect(getNodesByLabel(result, 'Enum')).toContain('State');
|
||||
});
|
||||
|
||||
it('extracts top-level functions from main.zig', () => {
|
||||
const fns = getNodesByLabel(result, 'Function');
|
||||
expect(fns).toContain('main');
|
||||
expect(fns).toContain('helper');
|
||||
});
|
||||
|
||||
it('extracts struct methods (tick, reset) as Methods', () => {
|
||||
const methods = getNodesByLabel(result, 'Method');
|
||||
expect(methods).toContain('tick');
|
||||
expect(methods).toContain('reset');
|
||||
});
|
||||
|
||||
it('resolves the relative @import("./pioneer.zig") to pioneer.zig', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const internal = imports.filter((e) => e.targetFilePath.endsWith('pioneer.zig'));
|
||||
expect(internal.length).toBeGreaterThan(0);
|
||||
expect(internal[0].sourceFilePath).toContain('main.zig');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue