mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
One commit per the bot's list so each item is easy to check off:
- parser-loader: Zig row gains `userSkippable: true`, so
`GITNEXUS_SKIP_OPTIONAL_GRAMMARS` (=1 or a list naming `zig`) disables it
at analyze time like swift/dart/kotlin — as `optional-grammars.ts` already
documented. Covered in parser-loader-skip-optional.test.ts.
- export-detection: `zigExportChecker` stops at the first declaration it
reaches. A non-`pub` fn inside `pub const T = struct {…}` was reported
exported because the walk continued up to the wrapper.
- import-resolvers/zig: a `.path = "."` dep normalizes to '' and no longer
grows a leading slash (`/src/main.zig` could never match).
- language-config: build.zig.zon parsing strips `//` comments string-aware
(a `//` inside `.url = "https://…"` survives) and matches braces while
skipping string literals, so a commented-out `.path` cannot declare a dep
and a `}` in a comment/string cannot truncate the block.
- method-extractors/configs/zig: the leading `self` receiver is excluded
from `parameters` (Rust parity). Fixing that exposed a worse bug: the
`parameters` node is a plain child of `function_declaration`, not a
`parameters:` field, so `childForFieldName('parameters')` was always null
and every Zig method had no parameters, no receiver and `isStatic: true`.
One `zigParameterList` helper now feeds all three readers.
- variable-extractors/configs/zig: container (`struct`/`enum`/`union`) and
`@import` bindings are skipped via the same predicate the scope captures
use (`isZigContainerOrImportBinding`), instead of the comment merely
claiming they were.
- tree-sitter-languages.test.ts: the "missing grammar" case now forces the
absent-binding path through the loader's runtime opt-out on a fresh module
instead of passing vacuously when the package is installed.
New: test/unit/zig-extractors.test.ts (exports, receiver/parameters,
variable guard); zig-import-resolver.test.ts gains the `.` dep, comment and
brace cases.
The `createFieldExtractor` heads-up needs no change: the added branch is
unreachable for every existing config (none has empty `bodyNodeTypes`).
183 lines
7 KiB
TypeScript
183 lines
7 KiB
TypeScript
/**
|
|
* Unit tests for the Zig import resolver, covering both relative-path
|
|
* imports and bare-name imports resolved through build.zig.zon.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import { resolveZigImportInternal } from '../../src/core/ingestion/import-resolvers/zig.js';
|
|
import { parseZigBuildZon } from '../../src/core/ingestion/language-config.js';
|
|
|
|
describe('resolveZigImportInternal', () => {
|
|
it('returns null for stdlib / builtin / root', () => {
|
|
const files = new Set<string>(['src/main.zig']);
|
|
expect(resolveZigImportInternal('src/main.zig', 'std', files)).toBeNull();
|
|
expect(resolveZigImportInternal('src/main.zig', 'builtin', files)).toBeNull();
|
|
expect(resolveZigImportInternal('src/main.zig', 'root', files)).toBeNull();
|
|
});
|
|
|
|
it('resolves "./foo.zig" relative to the importer', () => {
|
|
const files = new Set<string>(['src/main.zig', 'src/foo.zig']);
|
|
expect(resolveZigImportInternal('src/main.zig', './foo.zig', files)).toBe('src/foo.zig');
|
|
});
|
|
|
|
it('resolves "foo.zig" without a "./" prefix as filesystem-relative', () => {
|
|
const files = new Set<string>(['src/main.zig', 'src/foo.zig']);
|
|
expect(resolveZigImportInternal('src/main.zig', 'foo.zig', files)).toBe('src/foo.zig');
|
|
});
|
|
|
|
it('resolves "../sibling/file.zig" with parent traversal', () => {
|
|
const files = new Set<string>(['src/a/main.zig', 'src/b/util.zig']);
|
|
expect(resolveZigImportInternal('src/a/main.zig', '../b/util.zig', files)).toBe(
|
|
'src/b/util.zig',
|
|
);
|
|
});
|
|
|
|
it('returns null for a bare name when no build.zig.zon is supplied', () => {
|
|
const files = new Set<string>(['src/main.zig', 'vendor/ziggit/src/ziggit.zig']);
|
|
expect(resolveZigImportInternal('src/main.zig', 'ziggit', files)).toBeNull();
|
|
});
|
|
|
|
it('resolves a bare name via a `.path` build.zig.zon dep (`<root>/src/<name>.zig`)', () => {
|
|
const files = new Set<string>(['src/main.zig', 'vendor/ziggit/src/ziggit.zig']);
|
|
const buildZon = { pathDeps: new Map([['ziggit', 'vendor/ziggit']]) };
|
|
expect(resolveZigImportInternal('src/main.zig', 'ziggit', files, buildZon)).toBe(
|
|
'vendor/ziggit/src/ziggit.zig',
|
|
);
|
|
});
|
|
|
|
it('falls back to `<root>/src/main.zig` when no `<name>.zig` exists', () => {
|
|
const files = new Set<string>(['src/main.zig', 'vendor/ziggit/src/main.zig']);
|
|
const buildZon = { pathDeps: new Map([['ziggit', 'vendor/ziggit']]) };
|
|
expect(resolveZigImportInternal('src/main.zig', 'ziggit', files, buildZon)).toBe(
|
|
'vendor/ziggit/src/main.zig',
|
|
);
|
|
});
|
|
|
|
it('resolves a `.path = "."` dep against the repo root without a leading slash', () => {
|
|
// `normalizeDepPath('.')` is '' — the candidates used to become
|
|
// `/src/<name>.zig`, which can never match a repo-relative file key.
|
|
const files = new Set<string>(['src/main.zig', 'src/mylib.zig', 'examples/demo.zig']);
|
|
for (const dot of ['.', './']) {
|
|
const buildZon = { pathDeps: new Map([['mylib', dot]]) };
|
|
expect(resolveZigImportInternal('examples/demo.zig', 'mylib', files, buildZon)).toBe(
|
|
'src/mylib.zig',
|
|
);
|
|
}
|
|
// …and the `src/main.zig` fallback for a root dep too.
|
|
const buildZon = { pathDeps: new Map([['root_pkg', '.']]) };
|
|
expect(resolveZigImportInternal('examples/demo.zig', 'root_pkg', files, buildZon)).toBe(
|
|
'src/main.zig',
|
|
);
|
|
});
|
|
|
|
it('returns null for `.path` deps that escape the repo root (`..`)', () => {
|
|
const files = new Set<string>(['src/main.zig']);
|
|
const buildZon = { pathDeps: new Map([['ziggit', '../ziggit']]) };
|
|
expect(resolveZigImportInternal('src/main.zig', 'ziggit', files, buildZon)).toBeNull();
|
|
});
|
|
|
|
it('returns null when the conventional layout file is missing', () => {
|
|
const files = new Set<string>(['src/main.zig', 'vendor/ziggit/lib/something.zig']);
|
|
const buildZon = { pathDeps: new Map([['ziggit', 'vendor/ziggit']]) };
|
|
expect(resolveZigImportInternal('src/main.zig', 'ziggit', files, buildZon)).toBeNull();
|
|
});
|
|
|
|
it('returns null for an unknown bare name not in build.zig.zon', () => {
|
|
const files = new Set<string>(['src/main.zig']);
|
|
const buildZon = { pathDeps: new Map([['ziggit', 'vendor/ziggit']]) };
|
|
expect(resolveZigImportInternal('src/main.zig', 'mystery_pkg', files, buildZon)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('parseZigBuildZon', () => {
|
|
it('extracts `.path = "..."` deps and skips `.url`-based deps', () => {
|
|
const raw = `
|
|
.{
|
|
.name = "myproject",
|
|
.version = "0.1.0",
|
|
.dependencies = .{
|
|
.ziggit_pkg = .{
|
|
.url = "https://github.com/.../archive/abc.tar.gz",
|
|
.hash = "1220abc",
|
|
},
|
|
.local_dep = .{
|
|
.path = "../local_dep",
|
|
},
|
|
.vendor_dep = .{
|
|
.path = "vendor/foo",
|
|
},
|
|
},
|
|
.paths = .{ "" },
|
|
}
|
|
`;
|
|
const cfg = parseZigBuildZon(raw);
|
|
expect(cfg).not.toBeNull();
|
|
expect(cfg!.pathDeps.get('local_dep')).toBe('../local_dep');
|
|
expect(cfg!.pathDeps.get('vendor_dep')).toBe('vendor/foo');
|
|
// .url-based deps are intentionally absent
|
|
expect(cfg!.pathDeps.has('ziggit_pkg')).toBe(false);
|
|
});
|
|
|
|
it('ignores commented-out entries and `.path` lines', () => {
|
|
// A `// .path = "vendor/foo"` inside an entry used to be captured as a real
|
|
// dep because the regex ran over raw source. `//` inside a `.url` string
|
|
// must survive the strip — it is not a comment.
|
|
const raw = `
|
|
.{
|
|
.dependencies = .{
|
|
// .disabled = .{ .path = "vendor/disabled" },
|
|
.remote = .{
|
|
.url = "https://github.com/x/y/archive/abc.tar.gz",
|
|
// .path = "vendor/remote-override",
|
|
.hash = "1220abc",
|
|
},
|
|
.live = .{ .path = "vendor/live" }, // trailing note: .path = "nope"
|
|
},
|
|
}
|
|
`;
|
|
const cfg = parseZigBuildZon(raw);
|
|
expect(cfg).not.toBeNull();
|
|
expect([...cfg!.pathDeps.entries()]).toEqual([['live', 'vendor/live']]);
|
|
});
|
|
|
|
it('is not derailed by braces inside comments or string literals', () => {
|
|
// Every `{`/`}` used to count toward the block depth, so a `// }` comment or
|
|
// a `}` inside a string closed the `.dependencies` block early and dropped
|
|
// every dep after it.
|
|
const raw = `
|
|
.{
|
|
.dependencies = .{
|
|
.first = .{
|
|
.url = "https://example.com/weird}name{.tar.gz",
|
|
.hash = "1220x", // } stray brace in a comment
|
|
},
|
|
// } another one
|
|
.second = .{ .path = "vendor/second" },
|
|
.third = .{ .path = "vendor/third" },
|
|
},
|
|
.paths = .{ "" },
|
|
}
|
|
`;
|
|
const cfg = parseZigBuildZon(raw);
|
|
expect(cfg).not.toBeNull();
|
|
expect([...cfg!.pathDeps.entries()]).toEqual([
|
|
['second', 'vendor/second'],
|
|
['third', 'vendor/third'],
|
|
]);
|
|
});
|
|
|
|
it('returns null when no `.dependencies` block is present', () => {
|
|
const raw = `.{ .name = "x", .version = "0.0.0", .paths = .{""} }`;
|
|
expect(parseZigBuildZon(raw)).toBeNull();
|
|
});
|
|
|
|
it('returns null when the deps block has no `.path` entries', () => {
|
|
const raw = `
|
|
.{
|
|
.dependencies = .{
|
|
.only_url = .{ .url = "https://x", .hash = "1220y" },
|
|
},
|
|
}
|
|
`;
|
|
expect(parseZigBuildZon(raw)).toBeNull();
|
|
});
|
|
});
|