Skip to documentation
SLOP

tiny.smg.view

Reference tiny.smg view

Defined in tiny.smg.

API (20)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.smgview
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsprivate; no linktools.smg.src.storage.context.Suggestionsinitviewsuggestprivate; no linktools.smg.src.viewnormalizeIntoview.SuggestionQueryinit
Static calls · unresolved targets: 1 · external targets: 4.
Called byCallsNo direct callersprivate; no linktools.smg.src.viewboundedDistanceprivate; no linktools.smg.src.viewnormalizeIntoview.SuggestionQueryrank
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate; no linktools.smg.src.analysis.reportlayeringRowscli.subgraphprunecli.subgraphwriteSummarycommand.betweendirectEdgesconceptsanalyze+10 moreviewsortEdgesviewallEdges
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsprivate; no linktools.smg.src.analysis.overviewconnectedRowsprivate; no linktools.smg.src.analysis.overviewmoduleRowsprivate; no linktools.smg.src.analysis.reportclassMetricsWithIndexprivate; no linktools.smg.src.analysis.reportdeadCodeRowsWithIndexprivate; no linktools.smg.src.analysis.reportfeatureEnvyRowsWithIndex+18 moreviewsortNodesviewallNodes
Static calls · unresolved targets: 1 · external targets: 2.
Called byCallsNo direct callsprivate; no linktools.smg.src.rulescouplingBackEdgescan.core.edgeaddScanEdgetest; no linktools.smg.src.testtest: persisted clean rescan preserve...test; no linktools.smg.src.viewtest: contains edge frees long lookup...viewcontainsEdge
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallsNo direct callsprivate; no linktools.smg.src.cli.subgraphsubgraphDegreescommand.about.commandruntest; no linktools.smg.src.command.about.rendertest: about text renders coupling con...command.nodeshowcommand.queryrun+6 moreviewincoming
Static calls · unresolved targets: 1 · external targets: 3.
Called byCallscli.resolvegraphNodetest; no linktools.smg.src.viewtest: lookup widens resolve with path...viewresolveviewlookup
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallsNo direct callsprivate; no linktools.smg.src.analysis.overviewmoduleRowsprivate; no linktools.smg.src.cli.subgraphsubgraphDegreescommand.about.commandruntest; no linktools.smg.src.command.about.rendertest: about text renders coupling con...command.nodeshow+3 moreviewoutgoing
Static calls · unresolved targets: 1 · external targets: 3.
Called byCallsNo direct callsprivate; no linktools.smg.src.batch.mutationremoveprivate; no linktools.smg.src.batch.mutationupdateprivate; no linktools.smg.src.scan.pipeline.scancResolveQualifiedScopeprivate; no linktools.smg.src.viewcheckViewAllocationFailuresviewlookup+3 moreviewresolve
Static calls · unresolved targets: 1 · external targets: 6.
Called byCallsprivate; no linktools.smg.src.viewcheckViewAllocationFailurestest; no linktools.smg.src.viewtest: resolve suffix and validate edgeviewresolveviewresolveOne
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallscommand.analyze.commandrunprivate; no linktools.smg.src.viewcheckViewAllocationFailurestest; no linktools.smg.src.viewtest: scope prefix keeps matching nod...viewallEdgesviewallNodesviewscopePrefix
Static calls · unresolved targets: 0 · external targets: 6.
Called byCallsNo direct callsdiffgraphsprivate; no linktools.smg.src.difflinearEdgeDiffEdgeFindingPlansortviewallEdgesviewsortEdges
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsdiffgraphsviewallNodesviewsortNodes
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest; no linktools.smg.src.storage.contexttest: streamed suggestions preserve b...viewsuggestNodestest; no linktools.smg.src.viewtest: suggest matches partial leaves ...test; no linktools.smg.src.viewtest: suggest ranks case and separato...view.SuggestionQueryinitviewsuggest
Static calls · unresolved targets: 2 · external targets: 3.
Called byCallscli.resolvegraphNodetest; no linktools.smg.src.viewtest: suggest nodes reads names from ...viewsuggestviewsuggestNodes
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate; no linktools.smg.src.storage.contextsuggestedNameLessprivate; no linktools.smg.src.viewscoredLessviewsuggestionLessThan
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallscommand.validaterunprivate; no linktools.smg.src.viewcheckViewAllocationFailurestest; no linktools.smg.src.viewtest: resolve suffix and validate edgetest; no linktools.smg.src.viewtest: validate returns owned issue st...private; no linktools.smg.src.viewfreeStringsviewvalidate
Static calls · unresolved targets: 1 · external targets: 4.

Source: tools/smg/src/root.zig:37

zig
pub const view = @import("view.zig");

Source: tools/smg/src/view.zig

zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const graph_mod = @import("graph.zig");const limits_mod = @import("limits/root.zig");const model = @import("model.zig");const name_mod = @import("name.zig");const testing_limits = @import("root.zig").default_limits.suggestions;pub fn containsEdge(graph: graph_mod.Graph, source: []const u8, rel: []const u8, target: []const u8) !bool {    var stack: [4096]u8 = undefined;    const key_len = graph_mod.edgeKeyLen(source, rel, target);    const key = if (key_len <= stack.len)        graph_mod.edgeKeyInto(stack[0..key_len], source, rel, target)    else        try graph_mod.edgeKey(graph.allocator, source, rel, target);    defer if (key_len > stack.len) graph.allocator.free(key);    return graph.edge_index.contains(key);}pub fn resolve(graph: graph_mod.Graph, allocator: std.mem.Allocator, raw: []const u8) ![]const []const u8 {    var out: std.ArrayList([]const u8) = .empty;    errdefer out.deinit(allocator);    if (graph.node_index.contains(raw)) {        try out.append(allocator, raw);        return try out.toOwnedSlice(allocator);    }    if (graph_mod.suffixIndexReady(graph)) {        try graph_mod.appendSuffixMatches(graph, allocator, raw, &out);    } else {        for (graph.nodes.items) |node| {            if (name_mod.matchesSuffix(node.name, raw)) try out.append(allocator, node.name);        }    }    std.mem.sort([]const u8, out.items, {}, cmpString);    return try out.toOwnedSlice(allocator);}pub fn lookup(graph: graph_mod.Graph, allocator: std.mem.Allocator, raw: []const u8, limits: limits_mod.Suggestions) ![]const []const u8 {    const direct = try resolve(graph, allocator, raw);    if (direct.len != 0) return direct;    allocator.free(direct);    const names = try allocator.alloc([]const u8, graph.nodes.items.len);    defer allocator.free(names);    for (graph.nodes.items, 0..) |node, index| names[index] = node.name;    return try name_mod.fallbackMatches(allocator, names, raw, limits.fallback_match_count);}pub fn resolveOne(graph: graph_mod.Graph, allocator: std.mem.Allocator, raw: []const u8) ![]const u8 {    const matches = try resolve(graph, allocator, raw);    defer allocator.free(matches);    if (matches.len == 0) return graph_mod.Error.NodeNotFound;    if (matches.len > 1) return graph_mod.Error.AmbiguousName;    return matches[0];}pub const SuggestionRank = struct {    score: u8,    spread: usize,};pub const SuggestionQuery = struct {    allocator: std.mem.Allocator,    normalized: []u8,    normalized_len: usize,    leaf: []u8,    rows: []usize,    pub fn init(allocator: std.mem.Allocator, raw: []const u8, limits: limits_mod.Suggestions) !?SuggestionQuery {        const normalized = try allocator.alloc(u8, limits.normalized_name_bytes);        errdefer allocator.free(normalized);        const leaf = try allocator.alloc(u8, limits.normalized_name_bytes);        errdefer allocator.free(leaf);        const row_entries = try alloc_phase.capacity.add(usize, limits.normalized_name_bytes, 1);        const row_count = try alloc_phase.capacity.mul(usize, row_entries, 2);        const rows = try allocator.alloc(usize, row_count);        errdefer allocator.free(rows);        const value = normalizeInto(normalized, name_mod.leaf(raw)) orelse {            allocator.free(rows);            allocator.free(leaf);            allocator.free(normalized);            return null;        };        if (value.len == 0) {            allocator.free(rows);            allocator.free(leaf);            allocator.free(normalized);            return null;        }        return .{            .allocator = allocator,            .normalized = normalized,            .normalized_len = value.len,            .leaf = leaf,            .rows = rows,        };    }    pub fn deinit(self: *SuggestionQuery) void {        self.allocator.free(self.rows);        self.allocator.free(self.leaf);        self.allocator.free(self.normalized);        self.* = undefined;    }    pub fn rank(self: *SuggestionQuery, name: []const u8) ?SuggestionRank {        const query = self.normalized[0..self.normalized_len];        const leaf = normalizeInto(self.leaf, name_mod.leaf(name)) orelse return null;        if (leaf.len == 0) return null;        const score: u8 = if (std.mem.eql(u8, leaf, query))            0        else if (query.len >= 3 and std.mem.indexOf(u8, leaf, query) != null)            1        else if (boundedDistance(leaf, query, 2, self.rows)) |distance|            1 + distance        else if (leaf.len >= 3 and std.mem.indexOf(u8, query, leaf) != null)            4        else            return null;        return .{            .score = score,            .spread = @max(leaf.len, query.len) - @min(leaf.len, query.len),        };    }};const Scored = struct {    name: []const u8,    rank: SuggestionRank,};pub fn suggestionLessThan(    a_name: []const u8,    a_rank: SuggestionRank,    b_name: []const u8,    b_rank: SuggestionRank,) bool {    if (a_rank.score != b_rank.score) return a_rank.score < b_rank.score;    if (a_rank.spread != b_rank.spread) return a_rank.spread < b_rank.spread;    if (a_name.len != b_name.len) return a_name.len < b_name.len;    return std.mem.lessThan(u8, a_name, b_name);}pub fn suggest(allocator: std.mem.Allocator, names: []const []const u8, raw: []const u8, limit: usize, limits: limits_mod.Suggestions) ![]const []const u8 {    var query = (try SuggestionQuery.init(allocator, raw, limits)) orelse return &.{};    defer query.deinit();    var scored: std.ArrayList(Scored) = .empty;    defer scored.deinit(allocator);    for (names) |name| {        if (std.mem.eql(u8, name, raw)) continue;        const rank = query.rank(name) orelse continue;        try scored.append(allocator, .{ .name = name, .rank = rank });    }    std.mem.sort(Scored, scored.items, {}, scoredLess);    const count = @min(limit, scored.items.len);    const out = try allocator.alloc([]const u8, count);    for (scored.items[0..count], 0..) |entry, index| out[index] = entry.name;    return out;}pub fn suggestNodes(graph: graph_mod.Graph, allocator: std.mem.Allocator, raw: []const u8, limit: usize, limits: limits_mod.Suggestions) ![]const []const u8 {    const names = try allocator.alloc([]const u8, graph.nodes.items.len);    defer allocator.free(names);    for (graph.nodes.items, 0..) |node, index| names[index] = node.name;    return try suggest(allocator, names, raw, limit, limits);}fn normalizeInto(buffer: []u8, raw: []const u8) ?[]const u8 {    var length: usize = 0;    for (raw) |char| {        if (char == '_' or char == '-') continue;        if (length == buffer.len) return null;        buffer[length] = std.ascii.toLower(char);        length += 1;    }    return buffer[0..length];}fn boundedDistance(a: []const u8, b: []const u8, max: u8, rows: []usize) ?u8 {    const longer = if (a.len >= b.len) a else b;    const shorter = if (a.len >= b.len) b else a;    if (longer.len - shorter.len > max) return null;    const row_len = shorter.len + 1;    if (row_len > rows.len / 2) return null;    var previous = rows[0..row_len];    var current = rows[row_len .. row_len * 2];    for (0..shorter.len + 1) |index| previous[index] = @intCast(index);    for (longer, 0..) |char, row| {        current[0] = row + 1;        var row_min = current[0];        for (shorter, 0..) |other, column| {            const substitution = previous[column] + @intFromBool(char != other);            const insertion = current[column] + 1;            const deletion = previous[column + 1] + 1;            current[column + 1] = @min(substitution, @min(insertion, deletion));            row_min = @min(row_min, current[column + 1]);        }        if (row_min > max) return null;        @memcpy(previous[0 .. shorter.len + 1], current[0 .. shorter.len + 1]);    }    const distance = previous[shorter.len];    return if (distance <= max) @intCast(distance) else null;}fn scoredLess(_: void, a: Scored, b: Scored) bool {    return suggestionLessThan(a.name, a.rank, b.name, b.rank);}pub fn incoming(graph: graph_mod.Graph, allocator: std.mem.Allocator, name: []const u8, rel: ?[]const u8) ![]const model.Edge {    var out: std.ArrayList(model.Edge) = .empty;    errdefer out.deinit(allocator);    for (graph_mod.incomingEdges(&graph, name)) |edge_index| {        const edge = graph.edges.items[edge_index];        if (rel == null or std.mem.eql(u8, edge.rel, rel.?)) try out.append(allocator, edge);    }    std.mem.sort(model.Edge, out.items, {}, cmpIncoming);    return try out.toOwnedSlice(allocator);}pub fn outgoing(graph: graph_mod.Graph, allocator: std.mem.Allocator, name: []const u8, rel: ?[]const u8) ![]const model.Edge {    var out: std.ArrayList(model.Edge) = .empty;    errdefer out.deinit(allocator);    for (graph_mod.outgoingEdges(&graph, name)) |edge_index| {        const edge = graph.edges.items[edge_index];        if (rel == null or std.mem.eql(u8, edge.rel, rel.?)) try out.append(allocator, edge);    }    std.mem.sort(model.Edge, out.items, {}, cmpOutgoing);    return try out.toOwnedSlice(allocator);}pub fn allNodes(graph: graph_mod.Graph, allocator: std.mem.Allocator, type_name: ?[]const u8) ![]const model.Node {    var out: std.ArrayList(model.Node) = .empty;    errdefer out.deinit(allocator);    for (graph.nodes.items) |node| {        if (type_name == null or std.mem.eql(u8, node.type, type_name.?)) try out.append(allocator, node);    }    sortNodes(out.items);    return try out.toOwnedSlice(allocator);}pub fn sortNodes(nodes: []model.Node) void {    std.mem.sort(model.Node, nodes, {}, cmpNode);}pub fn allEdges(graph: graph_mod.Graph, allocator: std.mem.Allocator) ![]const model.Edge {    var out: std.ArrayList(model.Edge) = .empty;    errdefer out.deinit(allocator);    for (graph.edges.items) |edge| try out.append(allocator, edge);    sortEdges(out.items);    return try out.toOwnedSlice(allocator);}pub fn sortEdges(edges: []model.Edge) void {    std.mem.sort(model.Edge, edges, {}, cmpEdge);}pub fn validate(graph: graph_mod.Graph, allocator: std.mem.Allocator) ![]const []const u8 {    var out: std.ArrayList([]const u8) = .empty;    errdefer {        freeStrings(allocator, out.items);        out.deinit(allocator);    }    for (graph.edges.items) |edge| {        if (!graph.node_index.contains(edge.source)) {            const issue = try std.fmt.allocPrint(allocator, "dangling edge source: {s}", .{edge.source});            errdefer allocator.free(issue);            try out.append(allocator, issue);        }        if (!graph.node_index.contains(edge.target)) {            const issue = try std.fmt.allocPrint(allocator, "dangling edge target: {s}", .{edge.target});            errdefer allocator.free(issue);            try out.append(allocator, issue);        }    }    return try out.toOwnedSlice(allocator);}pub fn scopePrefix(graph: graph_mod.Graph, allocator: std.mem.Allocator, module_filter: []const u8) !graph_mod.Graph {    var scoped = graph_mod.init(allocator);    errdefer graph_mod.deinit(&scoped);    const prefix_owned = !std.mem.endsWith(u8, module_filter, ".");    const prefix = if (prefix_owned) try std.fmt.allocPrint(allocator, "{s}.", .{module_filter}) else module_filter;    defer if (prefix_owned) allocator.free(prefix);    const nodes = try allNodes(graph, allocator, null);    defer allocator.free(nodes);    for (nodes) |node| {        if (std.mem.eql(u8, node.name, module_filter) or std.mem.startsWith(u8, node.name, prefix)) try graph_mod.addNode(&scoped, node);    }    const edges = try allEdges(graph, allocator);    defer allocator.free(edges);    for (edges) |edge| {        if (scoped.node_index.contains(edge.source) and scoped.node_index.contains(edge.target)) try graph_mod.addEdge(&scoped, edge);    }    return scoped;}fn freeStrings(allocator: std.mem.Allocator, values: []const []const u8) void {    for (values) |value| allocator.free(value);}fn cmpString(_: void, a: []const u8, b: []const u8) bool {    return std.mem.lessThan(u8, a, b);}fn cmpNode(_: void, a: model.Node, b: model.Node) bool {    return std.mem.lessThan(u8, a.name, b.name);}fn cmpEdge(_: void, a: model.Edge, b: model.Edge) bool {    const source = std.mem.order(u8, a.source, b.source);    if (source != .eq) return source == .lt;    const rel = std.mem.order(u8, a.rel, b.rel);    if (rel != .eq) return rel == .lt;    return std.mem.lessThan(u8, a.target, b.target);}fn cmpIncoming(_: void, a: model.Edge, b: model.Edge) bool {    const rel = std.mem.order(u8, a.rel, b.rel);    if (rel != .eq) return rel == .lt;    return std.mem.lessThan(u8, a.source, b.source);}fn cmpOutgoing(_: void, a: model.Edge, b: model.Edge) bool {    const rel = std.mem.order(u8, a.rel, b.rel);    if (rel != .eq) return rel == .lt;    return std.mem.lessThan(u8, a.target, b.target);}test "resolve suffix and validate edge" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var graph = graph_mod.init(allocator);    const meta = try model.sourcePair(allocator, "manual");    try graph_mod.addNode(&graph, .{ .name = "pkg.mod", .type = model.NodeType.module, .metadata = meta });    try graph_mod.addNode(&graph, .{ .name = "pkg.mod.f", .type = model.NodeType.function, .metadata = meta });    try graph_mod.addEdge(&graph, .{ .source = "pkg.mod", .rel = model.RelType.contains, .target = "pkg.mod.f", .metadata = meta });    try std.testing.expectEqualStrings("pkg.mod.f", try resolveOne(graph, allocator, "f"));    const issues = try validate(graph, allocator);    try std.testing.expectEqual(@as(usize, 0), issues.len);}test "lookup widens resolve with path and subsequence stages" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var graph = graph_mod.init(allocator);    for ([_][]const u8{ "lib.pkg.src.core.merge", "lib.pkg.src.core", "lib.other.src.core.split", "tools.glom.src" }) |node_name| {        try graph_mod.addNode(&graph, .{ .name = node_name, .type = model.NodeType.module });    }    const exact = try lookup(graph, allocator, "lib.pkg.src.core.merge", testing_limits);    try std.testing.expectEqual(@as(usize, 1), exact.len);    const path = try lookup(graph, allocator, "lib/pkg/src/core/merge.zig", testing_limits);    try std.testing.expectEqual(@as(usize, 1), path.len);    try std.testing.expectEqualStrings("lib.pkg.src.core.merge", path[0]);    const relative = try lookup(graph, allocator, "src/core/merge.zig", testing_limits);    try std.testing.expectEqual(@as(usize, 1), relative.len);    try std.testing.expectEqualStrings("lib.pkg.src.core.merge", relative[0]);    const package_path = try lookup(graph, allocator, "tools/glom/src/index.zig", testing_limits);    try std.testing.expectEqual(@as(usize, 1), package_path.len);    try std.testing.expectEqualStrings("tools.glom.src", package_path[0]);    const subsequence = try lookup(graph, allocator, "pkg.core.merge", testing_limits);    try std.testing.expectEqual(@as(usize, 1), subsequence.len);    try std.testing.expectEqualStrings("lib.pkg.src.core.merge", subsequence[0]);    const strict = try resolve(graph, allocator, "pkg.core.merge");    try std.testing.expectEqual(@as(usize, 0), strict.len);    const absent = try lookup(graph, allocator, "no/such/file.zig", testing_limits);    try std.testing.expectEqual(@as(usize, 0), absent.len);}test "resolve matches agree between linear and indexed suffix lookup" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var graph = graph_mod.init(allocator);    for ([_][]const u8{ "app.main", "lib.main", "app.helper", "domain", "remain" }) |name| {        try graph_mod.addNode(&graph, .{ .name = name, .type = model.NodeType.function });    }    const linear = try resolve(graph, allocator, "main");    try std.testing.expectEqual(@as(usize, 2), linear.len);    try std.testing.expectEqualStrings("app.main", linear[0]);    try std.testing.expectEqualStrings("lib.main", linear[1]);    const linear_missing = try resolve(graph, allocator, "absent");    try std.testing.expectEqual(@as(usize, 0), linear_missing.len);    try graph_mod.buildSuffixIndex(&graph);    const indexed = try resolve(graph, allocator, "main");    try std.testing.expectEqual(@as(usize, 2), indexed.len);    try std.testing.expectEqualStrings("app.main", indexed[0]);    try std.testing.expectEqualStrings("lib.main", indexed[1]);    const exact = try resolve(graph, allocator, "app.main");    try std.testing.expectEqual(@as(usize, 1), exact.len);}test "suggest ranks case and separator variants above typos" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const names = [_][]const u8{        "store.IssueStateStore.mark_status",        "store.IssueStateStore.mark_stale",        "app.markstatus.render",        "lib.unrelated",    };    const exact = try suggest(allocator, &names, "markstatus", 5, testing_limits);    try std.testing.expectEqual(@as(usize, 1), exact.len);    try std.testing.expectEqualStrings("store.IssueStateStore.mark_status", exact[0]);    const typo = try suggest(allocator, &names, "mark_statsu", 5, testing_limits);    try std.testing.expect(typo.len >= 1);    try std.testing.expectEqualStrings("store.IssueStateStore.mark_status", typo[0]);    const none = try suggest(allocator, &names, "zzzqqq", 5, testing_limits);    try std.testing.expectEqual(@as(usize, 0), none.len);}test "suggest matches partial leaves and respects the limit" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    const names = [_][]const u8{        "compiler.lowering.emit",        "compiler.lowering.emitAll",        "compiler.lowering.emitOne",        "compiler.lowering.emitMany",        "compiler.lowering.emitSome",        "compiler.lowering.emitEach",    };    const partial = try suggest(allocator, &names, "emit", 5, testing_limits);    try std.testing.expectEqual(@as(usize, 5), partial.len);    try std.testing.expectEqualStrings("compiler.lowering.emit", partial[0]);}test "suggest nodes reads names from the graph" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var graph = graph_mod.init(allocator);    try graph_mod.addNode(&graph, .{ .name = "app.main", .type = model.NodeType.function });    try graph_mod.addNode(&graph, .{ .name = "app.helper", .type = model.NodeType.function });    const suggestions = try suggestNodes(graph, allocator, "app.mian", 5, testing_limits);    try std.testing.expectEqual(@as(usize, 1), suggestions.len);    try std.testing.expectEqualStrings("app.main", suggestions[0]);}test "contains edge frees long lookup keys" {    const source = &(@as([4100]u8, @splat('s')));    const target = &(@as([4100]u8, @splat('t')));    var graph = graph_mod.init(std.testing.allocator);    defer graph_mod.deinit(&graph);    try graph_mod.addNode(&graph, .{ .name = source, .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = target, .type = model.NodeType.function });    try graph_mod.addEdge(&graph, .{ .source = source, .rel = model.RelType.calls, .target = target });    try std.testing.expect(try containsEdge(graph, source, model.RelType.calls, target));}test "view helpers clean up allocation failures" {    try std.testing.checkAllAllocationFailures(        std.testing.allocator,        checkViewAllocationFailures,        .{},    );}fn checkViewAllocationFailures(allocator: std.mem.Allocator) !void {    var graph = graph_mod.init(allocator);    defer graph_mod.deinit(&graph);    try graph_mod.addNode(&graph, .{ .name = "app", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "app.main", .type = model.NodeType.function });    try graph_mod.addNode(&graph, .{ .name = "lib.util", .type = model.NodeType.function });    try graph_mod.addEdge(&graph, .{ .source = "app", .rel = model.RelType.contains, .target = "app.main" });    try graph_mod.addEdge(&graph, .{ .source = "app.main", .rel = model.RelType.calls, .target = "lib.util" });    const matches = try resolve(graph, allocator, "main");    defer allocator.free(matches);    try std.testing.expectEqual(@as(usize, 1), matches.len);    try std.testing.expectEqualStrings("app.main", try resolveOne(graph, allocator, "main"));    const incoming_edges = try incoming(graph, allocator, "app.main", null);    defer allocator.free(incoming_edges);    const outgoing_edges = try outgoing(graph, allocator, "app.main", null);    defer allocator.free(outgoing_edges);    const nodes = try allNodes(graph, allocator, null);    defer allocator.free(nodes);    const edges = try allEdges(graph, allocator);    defer allocator.free(edges);    const issues = try validate(graph, allocator);    defer {        freeStrings(allocator, issues);        allocator.free(issues);    }    var scoped = try scopePrefix(graph, allocator, "app");    defer graph_mod.deinit(&scoped);    try std.testing.expectEqual(@as(usize, 2), scoped.nodes.items.len);}test "validate returns owned issue strings" {    var graph = graph_mod.init(std.testing.allocator);    defer graph_mod.deinit(&graph);    try graph.edges.append(std.testing.allocator, .{        .source = "missing",        .rel = model.RelType.calls,        .target = "also_missing",    });    const issues = try validate(graph, std.testing.allocator);    defer {        freeStrings(std.testing.allocator, issues);        std.testing.allocator.free(issues);    }    try std.testing.expectEqual(@as(usize, 2), issues.len);}test "incoming edges sort by relationship then source" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var graph = graph_mod.init(allocator);    try graph_mod.addNode(&graph, .{ .name = "target", .type = model.NodeType.function });    try graph_mod.addNode(&graph, .{ .name = "owner", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "caller", .type = model.NodeType.function });    try graph_mod.addEdge(&graph, .{ .source = "owner", .rel = model.RelType.contains, .target = "target" });    try graph_mod.addEdge(&graph, .{ .source = "caller", .rel = model.RelType.calls, .target = "target" });    const incoming_edges = try incoming(graph, allocator, "target", null);    try std.testing.expectEqualStrings(model.RelType.calls, incoming_edges[0].rel);    try std.testing.expectEqualStrings("caller", incoming_edges[0].source);    try std.testing.expectEqualStrings(model.RelType.contains, incoming_edges[1].rel);    try std.testing.expectEqualStrings("owner", incoming_edges[1].source);}test "scope prefix keeps matching nodes and internal edges" {    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var graph = graph_mod.init(allocator);    try graph_mod.addNode(&graph, .{ .name = "app", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "app.main", .type = model.NodeType.function });    try graph_mod.addNode(&graph, .{ .name = "lib", .type = model.NodeType.module });    try graph_mod.addNode(&graph, .{ .name = "lib.util", .type = model.NodeType.function });    try graph_mod.addEdge(&graph, .{ .source = "app", .rel = model.RelType.contains, .target = "app.main" });    try graph_mod.addEdge(&graph, .{ .source = "app.main", .rel = model.RelType.calls, .target = "lib.util" });    const scoped = try scopePrefix(graph, allocator, "app");    try std.testing.expectEqual(@as(usize, 2), scoped.nodes.items.len);    try std.testing.expectEqual(@as(usize, 1), scoped.edges.items.len);    try std.testing.expect(graph_mod.getNode(&scoped, "app") != null);    try std.testing.expect(graph_mod.getNode(&scoped, "app.main") != null);}

Complete caller list for view.allEdges

15 direct callers.

Complete caller list for view.allNodes

23 direct callers.

Complete caller list for view.incoming

11 direct callers.

Complete caller list for view.outgoing

8 direct callers.

Complete caller list for view.resolve

8 direct callers.

Audit

Definitions21
Public names21
Members7
Version26.7.0
Revisiondaab053ee433