tiny.smg.scan.scan_metrics
Defined in scan.
API (29)
Actions
Public operations.
CallMetricsIndex.Capacity.deriveCallMetricsIndex.activateCallMetricsIndex.deinitCallMetricsIndex.fillCallMetricsIndex.getCallMetricsIndex.initbraceDeltacDeclarationMetadatacFunctionMetadatacMacroMetadatacRecordMetadatacallMetricsJsonidentByteindentationjavascriptFunctionMetadatapythonCommentLinescriptSpanMetadataskipQuotedtextFunctionMetadatatextMacroMetadatatextSpanMetadataupdateCallMetrics
Types and contracts
Public types and contracts.
CallMetricsIndexCallMetricsIndex.CapacityCallMetricsIndex.InitErrorCallMetricsIndex.LimitsFunctionMetricsMetricLanguage
Values and defaults
Public values and defaults.
Source
Source: tools/smg/src/scan/metrics.zig
zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const pretty_json = @import("pretty").json;const smg = @import("../root.zig");const core = @import("core/root.zig");const graph_mod = smg.graph;const lines_mod = smg.lines;const model = smg.model;const spans = smg.span;const text = smg.text;const tree = smg.tree;const SourceLines = lines_mod.SourceLines;pub fn pythonCommentLine(line: []const u8) bool { return std.mem.startsWith(u8, line, "#");}pub fn braceDelta(line: []const u8) i64 { var delta: i64 = 0; for (line) |byte| { if (byte == '{') delta += 1; if (byte == '}') delta -= 1; } return delta;}pub fn indentation(line: []const u8) usize { var spaces: usize = 0; for (line) |byte| { if (byte == ' ') spaces += 1 else if (byte == '\t') spaces += 4 else break; } return spaces;}fn zigBranch(kind: []const u8) bool { const branches = [_][]const u8{ "if_statement", "if_expression", "else_clause", "for_statement", "while_statement", "while_expression", "switch_expression", "switch_case", "catch_expression", "try_expression" }; for (branches) |branch| if (std.mem.eql(u8, kind, branch)) return true; return false;}fn zigNesting(kind: []const u8) bool { const nested = [_][]const u8{ "if_statement", "if_expression", "for_statement", "while_statement", "while_expression", "switch_expression" }; for (nested) |item| if (std.mem.eql(u8, kind, item)) return true; return false;}pub fn scriptSpanMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair { const pairs = try allocator.alloc(model.Pair, 3); pairs[0] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) }; pairs[1] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) }; pairs[2] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}pub fn javascriptFunctionMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, metric_inputs: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair { const metrics = try computeJavaScriptMetrics(scratch, source, node); const pairs = try allocator.alloc(model.Pair, 4); pairs[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try metricsJson(metric_inputs, metrics, 0, 0), .json = true }; pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) }; pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) }; pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}fn computeJavaScriptMetrics(allocator: std.mem.Allocator, source: []const u8, node: tree.Node) !FunctionMetrics { var metrics = FunctionMetrics{ .lines_of_code = tree.endLine(node) - tree.startLine(node) + 1 }; if (tree.childByField(node, "parameters")) |parameters| metrics.parameter_count = countNamedNonComment(parameters); if (metrics.parameter_count == 0) { if (tree.childByField(node, "formal_parameters")) |parameters| metrics.parameter_count = countNamedNonComment(parameters); } if (tree.childByField(node, "body")) |body| { var increments: i64 = 0; const children = tree.childCount(body); for (0..children) |raw_index| try walkMetricTree(allocator, source, tree.child(body, @intCast(raw_index)), .javascript, 0, &metrics, &increments); metrics.cyclomatic_complexity = 1 + increments; } return metrics;}fn countNamedNonComment(node: tree.Node) i64 { var count_value: i64 = 0; const children = tree.childCount(node); for (0..children) |raw_index| { const child_node = tree.child(node, @intCast(raw_index)); if (tree.isNamed(child_node) and !hashSkip(tree.kind(child_node))) count_value += 1; } return count_value;}fn javascriptBranch(kind: []const u8) bool { const branches = [_][]const u8{ "if_statement", "else_clause", "for_statement", "while_statement", "do_statement", "for_in_statement", "for_of_statement", "switch_case", "catch_clause", "ternary_expression" }; for (branches) |branch| if (std.mem.eql(u8, kind, branch)) return true; return false;}fn javascriptNesting(kind: []const u8) bool { const nested = [_][]const u8{ "if_statement", "for_statement", "while_statement", "do_statement", "for_in_statement", "for_of_statement", "try_statement", "switch_statement" }; for (nested) |item| if (std.mem.eql(u8, kind, item)) return true; return false;}fn javascriptLogicalOperator(source: []const u8, node: tree.Node) bool { if (!std.mem.eql(u8, tree.kind(node), "binary_expression")) return false; const children = tree.childCount(node); for (0..children) |raw_index| { const child_node = tree.child(node, @intCast(raw_index)); if (tree.isNamed(child_node)) continue; const token = tree.slice(source, child_node); if (std.mem.eql(u8, token, "&&") or std.mem.eql(u8, token, "||") or std.mem.eql(u8, token, "??")) return true; } const span = tree.slice(source, node); return countNeedleOutsideQuotes(span, "&&") != 0 or countNeedleOutsideQuotes(span, "||") != 0 or countNeedleOutsideQuotes(span, "??") != 0;}pub fn cFunctionMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, metric_inputs: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair { const metrics = try computeCMetrics(scratch, source, node); const pairs = try allocator.alloc(model.Pair, 4); pairs[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try metricsJson(metric_inputs, metrics, 0, 0), .json = true }; pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) }; pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) }; pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}pub fn cDeclarationMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair { const pairs = try allocator.alloc(model.Pair, 3); pairs[0] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) }; pairs[1] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) }; pairs[2] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}pub fn cRecordMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, source: []const u8, node: tree.Node, c_kind: []const u8) ![]const model.Pair { const pairs = try allocator.alloc(model.Pair, 4); pairs[0] = .{ .key = try allocator.dupe(u8, "c_kind"), .value = try allocator.dupe(u8, c_kind) }; pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) }; pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) }; pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}pub fn cMacroMetadata(allocator: std.mem.Allocator, scratch: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const model.Pair { const pairs = try allocator.alloc(model.Pair, 4); pairs[0] = .{ .key = try allocator.dupe(u8, "macro"), .value = try allocator.dupe(u8, "true"), .json = true }; pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try contentHash(allocator, source, node) }; pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try structureHash(allocator, scratch, node) }; pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}fn computeCMetrics(allocator: std.mem.Allocator, source: []const u8, node: tree.Node) !FunctionMetrics { var metrics = FunctionMetrics{ .lines_of_code = tree.endLine(node) - tree.startLine(node) + 1 }; if (tree.childByField(node, "parameters")) |parameters| metrics.parameter_count = countNamedNonComment(parameters); if (metrics.parameter_count == 0) { if (tree.childByField(node, "formal_parameters")) |parameters| metrics.parameter_count = countNamedNonComment(parameters); } if (tree.childByField(node, "body")) |body| { var increments: i64 = 0; const children = tree.childCount(body); for (0..children) |raw_index| try walkMetricTree(allocator, source, tree.child(body, @intCast(raw_index)), .c, 0, &metrics, &increments); metrics.cyclomatic_complexity = 1 + increments; } return metrics;}fn cBranch(kind: []const u8) bool { const branches = [_][]const u8{ "if_statement", "else_clause", "for_statement", "while_statement", "do_statement", "switch_statement", "case_statement", "conditional_expression" }; for (branches) |branch| if (std.mem.eql(u8, kind, branch)) return true; return false;}fn cNesting(kind: []const u8) bool { const nested = [_][]const u8{ "if_statement", "for_statement", "while_statement", "do_statement", "switch_statement" }; for (nested) |item| if (std.mem.eql(u8, kind, item)) return true; return false;}fn cLogicalOperator(source: []const u8, node: tree.Node) bool { if (!std.mem.eql(u8, tree.kind(node), "binary_expression")) return false; const children = tree.childCount(node); for (0..children) |raw_index| { const child_node = tree.child(node, @intCast(raw_index)); if (tree.isNamed(child_node)) continue; const token = tree.slice(source, child_node); if (std.mem.eql(u8, token, "&&") or std.mem.eql(u8, token, "||")) return true; } return false;}pub fn textFunctionMetadata(allocator: std.mem.Allocator, metric_inputs: std.mem.Allocator, source_lines: SourceLines, line: i64, end_line: i64, metric_language: MetricLanguage) ![]const model.Pair { const source_span = spans.trimmed(source_lines, line, end_line); const metrics = computeTextMetrics(source_span, metric_language); const pairs = try allocator.alloc(model.Pair, 4); pairs[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try metricsJson(metric_inputs, metrics, 0, 0), .json = true }; pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try textContentHash(allocator, source_span) }; pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try textStructureHash(allocator, source_span) }; pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}pub fn textSpanMetadata(allocator: std.mem.Allocator, source_lines: SourceLines, line: i64, end_line: i64, c_kind: ?[]const u8) ![]const model.Pair { const source_span = spans.trimmed(source_lines, line, end_line); const include_kind = c_kind != null and !std.mem.eql(u8, c_kind.?, "class"); const count_value: usize = if (include_kind) 4 else 3; const pairs = try allocator.alloc(model.Pair, count_value); var index: usize = 0; if (include_kind) { pairs[index] = .{ .key = try allocator.dupe(u8, "c_kind"), .value = try allocator.dupe(u8, c_kind.?) }; index += 1; } pairs[index] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try textContentHash(allocator, source_span) }; index += 1; pairs[index] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try textStructureHash(allocator, source_span) }; index += 1; pairs[index] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}pub fn textMacroMetadata(allocator: std.mem.Allocator, source_lines: SourceLines, line: i64, end_line: i64) ![]const model.Pair { const source_span = spans.trimmed(source_lines, line, end_line); const pairs = try allocator.alloc(model.Pair, 4); pairs[0] = .{ .key = try allocator.dupe(u8, "macro"), .value = try allocator.dupe(u8, "true"), .json = true }; pairs[1] = .{ .key = try allocator.dupe(u8, "content_hash"), .value = try textContentHash(allocator, source_span) }; pairs[2] = .{ .key = try allocator.dupe(u8, "structure_hash"), .value = try textStructureHash(allocator, source_span) }; pairs[3] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; return pairs;}fn computeTextMetrics(span: []const u8, metric_language: MetricLanguage) FunctionMetrics { if (metric_language == .python) return computePythonTextMetrics(span); var state = textMetricState(span); var lines = std.mem.splitScalar(u8, span, '\n'); while (lines.next()) |raw_line| { updateBraceTextMetricLine(&state, raw_line, metric_language); } return finishTextMetricState(&state);}fn computePythonTextMetrics(span: []const u8) FunctionMetrics { var state = textMetricState(span); var lines = std.mem.splitScalar(u8, span, '\n'); while (lines.next()) |raw_line| { updatePythonTextMetricLine(&state, raw_line); } return finishTextMetricState(&state);}fn textMetricState(span: []const u8) TextMetricState { return .{ .metrics = .{ .lines_of_code = textLineCount(span), .parameter_count = textParameterCount(span), }, };}fn finishTextMetricState(state: *TextMetricState) FunctionMetrics { state.metrics.cyclomatic_complexity = 1 + state.increments; return state.metrics;}fn updateBraceTextMetricLine(state: *TextMetricState, raw_line: []const u8, metric_language: MetricLanguage) void { const trimmed = text.trim(raw_line); if (trimmed.len == 0) return; closeBraceTextMetricDepth(state, trimmed); const nesting_depth = if (state.depth > 0) state.depth - 1 else 0; applyBraceTextBranches(state, trimmed, metric_language, nesting_depth); applyTextLogicalMetrics(state, trimmed, metric_language); if (metric_language != .zig) state.metrics.return_count += countKeyword(trimmed, "return"); openBraceTextMetricDepth(state, trimmed);}fn updatePythonTextMetricLine(state: *TextMetricState, raw_line: []const u8) void { const trimmed = text.trim(raw_line); if (trimmed.len == 0 or pythonCommentLine(trimmed)) return; const level = pythonTextIndentLevel(raw_line); const branch_depth = if (level > 0) level - 1 else 0; applyPythonTextBranches(state, trimmed, level, branch_depth); applyTextLogicalMetrics(state, trimmed, .python); state.metrics.return_count += countKeywordOutsideQuotes(trimmed, "return");}fn closeBraceTextMetricDepth(state: *TextMetricState, trimmed: []const u8) void { state.depth -= leadingCloseBraces(trimmed); if (state.depth < 0) state.depth = 0;}fn openBraceTextMetricDepth(state: *TextMetricState, trimmed: []const u8) void { state.depth += braceDelta(trimmed); if (state.depth < 0) state.depth = 0;}fn applyBraceTextBranches(state: *TextMetricState, trimmed: []const u8, metric_language: MetricLanguage, nesting_depth: i64) void { const branches = textBranchCount(trimmed, metric_language); if (branches == 0) return; state.increments += branches; state.metrics.cognitive_complexity += branches * (1 + nesting_depth); const branch_depth = nesting_depth + branches; if (branch_depth > state.metrics.max_nesting_depth) state.metrics.max_nesting_depth = branch_depth;}fn applyPythonTextBranches(state: *TextMetricState, trimmed: []const u8, level: i64, branch_depth: i64) void { const branches = pythonTextBranchCount(trimmed); if (branches == 0) return; state.increments += branches; state.metrics.cognitive_complexity += branches * (1 + branch_depth); if (level > state.metrics.max_nesting_depth) state.metrics.max_nesting_depth = level;}fn applyTextLogicalMetrics(state: *TextMetricState, trimmed: []const u8, metric_language: MetricLanguage) void { const logical = textLogicalCount(trimmed, metric_language); state.increments += logical; state.metrics.cognitive_complexity += logical;}fn pythonTextIndentLevel(raw_line: []const u8) i64 { return @intCast(indentation(raw_line) / 4);}fn pythonTextBranchCount(line: []const u8) i64 { return countKeywordOutsideQuotes(line, "if") + countKeywordOutsideQuotes(line, "elif") + countKeywordOutsideQuotes(line, "for") + countKeywordOutsideQuotes(line, "while") + countKeywordOutsideQuotes(line, "except") + countKeywordOutsideQuotes(line, "with") + countKeywordOutsideQuotes(line, "match") + countKeywordOutsideQuotes(line, "case");}fn textLineCount(span: []const u8) i64 { if (span.len == 0) return 0; var count_value: i64 = 1; for (span) |byte| { if (byte == '\n') count_value += 1; } return count_value;}fn textParameterCount(span: []const u8) i64 { const open = std.mem.indexOfScalar(u8, span, '(') orelse return 0; const rest = span[open + 1 ..]; const close_rel = std.mem.indexOfScalar(u8, rest, ')') orelse return 0; const raw_parameters = text.trim(rest[0..close_rel]); if (raw_parameters.len == 0 or std.mem.eql(u8, raw_parameters, "void")) return 0; var count_value: i64 = 0; var split = std.mem.splitScalar(u8, raw_parameters, ','); while (split.next()) |raw| { const parameter = text.trim(raw); if (parameter.len == 0 or std.mem.eql(u8, parameter, "void")) continue; count_value += 1; } return count_value;}fn textBranchCount(line: []const u8, metric_language: MetricLanguage) i64 { _ = metric_language; return countKeyword(line, "if") + countKeyword(line, "for") + countKeyword(line, "while") + countKeyword(line, "catch") + countKeyword(line, "case") + countKeyword(line, "switch");}fn textLogicalCount(line: []const u8, metric_language: MetricLanguage) i64 { var count_value = countNeedleOutsideQuotes(line, "&&") + countNeedleOutsideQuotes(line, "||"); if (metric_language == .javascript) count_value += countNeedleOutsideQuotes(line, "??"); if (metric_language == .zig or metric_language == .python) count_value += countKeywordOutsideQuotes(line, "and") + countKeywordOutsideQuotes(line, "or"); return count_value;}fn countNeedleOutsideQuotes(source: []const u8, needle: []const u8) i64 { var count_value: i64 = 0; var offset: usize = 0; while (offset < source.len) { if (source[offset] == '"' or source[offset] == '\'') { offset = skipQuoted(source, offset); continue; } if (offset + needle.len <= source.len and std.mem.eql(u8, source[offset .. offset + needle.len], needle)) { count_value += 1; offset += needle.len; continue; } offset += 1; } return count_value;}fn countKeywordOutsideQuotes(source: []const u8, keyword: []const u8) i64 { var count_value: i64 = 0; var offset: usize = 0; while (offset < source.len) { if (source[offset] == '"' or source[offset] == '\'') { offset = skipQuoted(source, offset); continue; } if (offset + keyword.len <= source.len and std.mem.eql(u8, source[offset .. offset + keyword.len], keyword)) { const before_ok = offset == 0 or !identByte(source[offset - 1]); const after = offset + keyword.len; const after_ok = after == source.len or !identByte(source[after]); if (before_ok and after_ok) count_value += 1; offset = after; continue; } offset += 1; } return count_value;}fn countNeedle(source: []const u8, needle: []const u8) i64 { var count_value: i64 = 0; var offset: usize = 0; while (offset < source.len) { const rel = std.mem.indexOf(u8, source[offset..], needle) orelse break; count_value += 1; offset += rel + needle.len; } return count_value;}fn countKeyword(source: []const u8, keyword: []const u8) i64 { var count_value: i64 = 0; var offset: usize = 0; while (offset < source.len) { const rel = std.mem.indexOf(u8, source[offset..], keyword) orelse break; const index = offset + rel; const before_ok = index == 0 or !identByte(source[index - 1]); const after = index + keyword.len; const after_ok = after == source.len or !identByte(source[after]); if (before_ok and after_ok) count_value += 1; offset = after; } return count_value;}fn leadingCloseBraces(line: []const u8) i64 { var count_value: i64 = 0; for (line) |byte| { if (byte == '}') { count_value += 1; } else if (!std.ascii.isWhitespace(byte)) { break; } } return count_value;}fn textContentHash(allocator: std.mem.Allocator, span: []const u8) ![]const u8 { var hasher = std.hash.XxHash64.init(0); hasher.update(span); return try hex64(allocator, hasher.final());}fn textStructureHash(allocator: std.mem.Allocator, span: []const u8) ![]const u8 { var hasher = std.hash.XxHash64.init(0); var index: usize = 0; while (index < span.len) { const byte = span[index]; if (std.ascii.isWhitespace(byte)) { index += 1; } else if (identByte(byte)) { hasher.update("_"); index += 1; while (index < span.len and identByte(span[index])) index += 1; } else if (std.ascii.isDigit(byte)) { hasher.update("_"); index += 1; while (index < span.len and (std.ascii.isDigit(span[index]) or span[index] == '.')) index += 1; } else if (byte == '"' or byte == '\'') { hasher.update("_"); index = skipQuoted(span, index); } else { var single = [_]u8{byte}; hasher.update(&single); index += 1; } } return try hex64(allocator, hasher.final());}pub fn skipQuoted(source: []const u8, start: usize) usize { const quote = source[start]; var index = start + 1; while (index < source.len) : (index += 1) { if (source[index] == '\\') { if (index + 1 < source.len) index += 1; } else if (source[index] == quote) { return index + 1; } } return source.len;}fn zigLogicalOperator(source: []const u8, node: tree.Node) bool { if (!std.mem.eql(u8, tree.kind(node), "binary_expression")) return false; const children = tree.childCount(node); for (0..children) |raw_index| { const child_node = tree.child(node, @intCast(raw_index)); if (tree.isNamed(child_node)) continue; const token = tree.slice(source, child_node); if (std.mem.eql(u8, token, "and") or std.mem.eql(u8, token, "or")) return true; } return false;}pub const FunctionMetrics = struct { cyclomatic_complexity: i64 = 1, cognitive_complexity: i64 = 0, max_nesting_depth: i64 = 0, lines_of_code: i64 = 0, parameter_count: i64 = 0, return_count: i64 = 0, unresolved_call_targets: ?i64 = null, external_call_targets: ?i64 = null,};const TextMetricState = struct { metrics: FunctionMetrics, increments: i64 = 0, depth: i64 = 0,};pub const MetricLanguage = enum { zig, javascript, c, python,};const MetricFrame = struct { node: tree.Node, depth: i64,};fn walkMetricTree(allocator: std.mem.Allocator, source: []const u8, root: tree.Node, metric_language: MetricLanguage, depth: i64, metrics: *FunctionMetrics, increments: *i64) !void { var stack: std.ArrayList(MetricFrame) = .empty; defer stack.deinit(allocator); try stack.append(allocator, .{ .node = root, .depth = depth }); while (stack.pop()) |frame| { const node = frame.node; const node_kind = tree.kind(node); if (metricSkip(metric_language, node_kind)) continue; if (metricBranch(metric_language, node_kind)) { increments.* += 1; metrics.cognitive_complexity += 1 + frame.depth; } if (metricLogicalOperator(metric_language, source, node, node_kind)) { increments.* += 1; metrics.cognitive_complexity += 1; } if (metricReturn(metric_language, node_kind)) metrics.return_count += 1; const child_depth = if (metricNesting(metric_language, node_kind)) frame.depth + 1 else frame.depth; if (child_depth > metrics.max_nesting_depth) metrics.max_nesting_depth = child_depth; var index = tree.childCount(node); while (index != 0) { index -= 1; try stack.append(allocator, .{ .node = tree.child(node, @intCast(index)), .depth = child_depth }); } }}fn metricSkip(metric_language: MetricLanguage, kind: []const u8) bool { return switch (metric_language) { .zig => std.mem.eql(u8, kind, "function_declaration"), .javascript => std.mem.eql(u8, kind, "function_declaration") or std.mem.eql(u8, kind, "method_definition") or std.mem.eql(u8, kind, "arrow_function") or std.mem.eql(u8, kind, "class_declaration"), .c => std.mem.eql(u8, kind, "function_definition") or std.mem.eql(u8, kind, "class_definition") or std.mem.eql(u8, kind, "class_declaration"), .python => hashSkip(kind) or hashNormalize(kind) or std.mem.eql(u8, kind, "function_definition") or std.mem.eql(u8, kind, "class_definition") or std.mem.eql(u8, kind, "decorated_definition"), };}fn metricBranch(metric_language: MetricLanguage, kind: []const u8) bool { return switch (metric_language) { .zig => zigBranch(kind), .javascript => javascriptBranch(kind), .c => cBranch(kind), .python => pythonBranch(kind), };}fn metricLogicalOperator(metric_language: MetricLanguage, source: []const u8, node: tree.Node, kind: []const u8) bool { return switch (metric_language) { .zig => zigLogicalOperator(source, node), .javascript => javascriptLogicalOperator(source, node), .c => cLogicalOperator(source, node), .python => pythonLogicalOperator(kind), };}fn metricReturn(metric_language: MetricLanguage, kind: []const u8) bool { return switch (metric_language) { .zig => false, .javascript, .c, .python => std.mem.eql(u8, kind, "return_statement"), };}fn metricNesting(metric_language: MetricLanguage, kind: []const u8) bool { return switch (metric_language) { .zig => zigNesting(kind), .javascript => javascriptNesting(kind), .c => cNesting(kind), .python => pythonNesting(kind), };}fn pythonBranch(kind: []const u8) bool { const branches = [_][]const u8{ "if_statement", "elif_clause", "for_statement", "while_statement", "except_clause", "with_statement", "conditional_expression", "match_statement", "case_clause" }; for (branches) |branch| if (std.mem.eql(u8, kind, branch)) return true; return false;}fn pythonLogicalOperator(kind: []const u8) bool { return std.mem.eql(u8, kind, "boolean_operator");}fn pythonNesting(kind: []const u8) bool { const nested = [_][]const u8{ "if_statement", "for_statement", "while_statement", "try_statement", "with_statement", "match_statement" }; for (nested) |item| if (std.mem.eql(u8, kind, item)) return true; return false;}fn metricsJson( allocator: std.mem.Allocator, metrics: FunctionMetrics, fan_in: i64, fan_out: i64,) ![]const u8 { var out: std.Io.Writer.Allocating = .init(allocator); errdefer out.deinit(); var json = pretty_json.Writer.init(&out.writer, .minified); const object = try json.object(); try object.field("cyclomatic_complexity", metrics.cyclomatic_complexity); try object.field("cognitive_complexity", metrics.cognitive_complexity); try object.field("max_nesting_depth", metrics.max_nesting_depth); try object.field("lines_of_code", metrics.lines_of_code); try object.field("parameter_count", metrics.parameter_count); try object.field("return_count", metrics.return_count); try object.field("fan_in", fan_in); try object.field("fan_out", fan_out); try writeResolutionMetrics(object, metrics); try object.end(); return try out.toOwnedSlice();}fn metricsJsonWithCalls( allocator: std.mem.Allocator, metrics: FunctionMetrics, fan_in: i64, fan_out: i64,) ![]const u8 { return try metricsJson(allocator, metrics, fan_in, fan_out);}pub fn callMetricsJson( allocator: std.mem.Allocator, fan_in: i64, fan_out: i64, metrics: FunctionMetrics,) ![]const u8 { var out: std.Io.Writer.Allocating = .init(allocator); errdefer out.deinit(); var json = pretty_json.Writer.init(&out.writer, .minified); const object = try json.object(); try object.field("fan_in", fan_in); try object.field("fan_out", fan_out); try writeResolutionMetrics(object, metrics); try object.end(); return try out.toOwnedSlice();}fn writeResolutionMetrics( object: pretty_json.Object, metrics: FunctionMetrics,) !void { if (metrics.unresolved_call_targets) |count| { try object.field("unresolved_call_targets", count); } if (metrics.external_call_targets) |count| { try object.field("external_call_targets", count); }}fn contentHash(allocator: std.mem.Allocator, source: []const u8, node: tree.Node) ![]const u8 { var hasher = std.hash.XxHash64.init(0); hasher.update(source[tree.startByte(node)..tree.endByte(node)]); return try hex64(allocator, hasher.final());}fn structureHash(allocator: std.mem.Allocator, scratch: std.mem.Allocator, node: tree.Node) ![]const u8 { var hasher = std.hash.XxHash64.init(0); try structureHashWalk(scratch, node, &hasher); return try hex64(allocator, hasher.final());}const HashFrame = struct { node: tree.Node, close: bool = false,};fn structureHashWalk(allocator: std.mem.Allocator, root: tree.Node, hasher: *std.hash.XxHash64) !void { var stack: std.ArrayList(HashFrame) = .empty; defer stack.deinit(allocator); try stack.append(allocator, .{ .node = root }); while (stack.pop()) |frame| { if (frame.close) { hasher.update(")"); continue; } const node = frame.node; const node_kind = tree.kind(node); if (hashSkip(node_kind)) continue; if (hashNormalize(node_kind)) { hasher.update("_"); continue; } hasher.update(node_kind); hasher.update("("); try stack.append(allocator, .{ .node = node, .close = true }); var index = tree.childCount(node); while (index != 0) { index -= 1; const child_node = tree.child(node, @intCast(index)); if (!hashSkip(tree.kind(child_node))) try stack.append(allocator, .{ .node = child_node }); } }}fn hashSkip(kind: []const u8) bool { return std.mem.eql(u8, kind, "comment") or std.mem.eql(u8, kind, "line_comment") or std.mem.eql(u8, kind, "block_comment");}fn hashNormalize(kind: []const u8) bool { const normalized = [_][]const u8{ "identifier", "type_identifier", "field_identifier", "string", "string_content", "string_literal", "integer", "integer_literal", "float", "float_literal", "number", "true", "false", "none", "null" }; for (normalized) |item| if (std.mem.eql(u8, kind, item)) return true; return false;}fn hex64(allocator: std.mem.Allocator, value: u64) ![]const u8 { var buf: [16]u8 = undefined; const hex = "0123456789abcdef"; var current = value; var index = buf.len; while (index > 0) { index -= 1; buf[index] = hex[@as(usize, @intCast(current & 0xf))]; current >>= 4; } return try allocator.dupe(u8, &buf);}pub fn identByte(byte: u8) bool { return std.ascii.isAlphanumeric(byte) or byte == '_';}const CallMetricsCounts = struct { fan_in: usize = 0, fan_out: usize = 0, unresolved_targets: usize = 0, external_targets: usize = 0, resolution_known: bool = false,};fn fileMeasured( measured_files: []const []const u8, file: ?[]const u8,) bool { const name = file orelse return false; var lower: usize = 0; var upper = measured_files.len; while (lower < upper) { const middle = lower + (upper - lower) / 2; switch (std.mem.order(u8, measured_files[middle], name)) { .eq => return true, .lt => lower = middle + 1, .gt => upper = middle, } } return false;}fn failureLess( _: void, left: core.ResolutionFailure, right: core.ResolutionFailure,) bool { const source_order = std.mem.order(u8, left.source, right.source); if (source_order != .eq) return source_order == .lt; const left_bucket = @backingInt(left.bucket); const right_bucket = @backingInt(right.bucket); if (left_bucket != right_bucket) return left_bucket < right_bucket; return std.mem.lessThan(u8, left.target, right.target);}fn sameFailure( left: core.ResolutionFailure, right: core.ResolutionFailure,) bool { return left.bucket == right.bucket and std.mem.eql(u8, left.source, right.source) and std.mem.eql(u8, left.target, right.target);}pub const CallMetricsIndex = struct { pub const Limits = struct { nodes: usize, failures: usize, }; pub const Capacity = struct { counts: usize, failures: usize, bytes: usize, pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity { const count_bytes = std.math.mul( usize, limits.nodes, @sizeOf(CallMetricsCounts), ) catch return error.CapacityOverflow; const failure_bytes = std.math.mul( usize, limits.failures, @sizeOf(core.ResolutionFailure), ) catch return error.CapacityOverflow; return .{ .counts = limits.nodes, .failures = limits.failures, .bytes = std.math.add( usize, count_bytes, failure_bytes, ) catch return error.CapacityOverflow, }; } }; pub const InitError = std.mem.Allocator.Error || error{CapacityOverflow}; pub const claim: alloc_phase.capacity.Declaration = .{ .source = .{ .id = "smg.call_metrics_index", .kind = .phase_static, .limit_source = .caller, .storage = .{ .covered = &.{ .{ .id = "one_exact_call_and_resolution_count_record_per_final_graph_node", .lifetime = .steady, .detail = "one exact call and resolution count record per final graph node", }, .{ .id = "one_sortable_view_record_per_unresolved_deferred_call_target", .lifetime = .steady, .detail = "one sortable view record per unresolved deferred call target", }, }, .excluded = &.{ "retained graph nodes edges names and metadata", "reset-per-node metrics JSON parse scratch", "final metrics JSON and metadata pair storage", }, }, .capacity = .{ .inputs = &.{ alloc_phase.capacity.bindInput(Limits, "nodes", "nodes"), alloc_phase.capacity.bindInput(Limits, "failures", "failures"), }, .type_selectors = &.{ alloc_phase.capacity.bindType(CallMetricsCounts, "callmetricscounts"), alloc_phase.capacity.bindType(core.ResolutionFailure, "resolution_failure"), }, .nodes = &.{ .{ .input = 0 }, .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 } } }, .{ .input = 1 }, .{ .scale = .{ .node = 2, .coefficient = .{ .size_of_concrete_type = 1 } } }, .{ .add = .{ .left = 1, .right = 3 } }, }, .assertions = &.{.{ .scope = .closure_total, .measure = .retained, .relation = .exact, .expression = 4, }}, }, .overload = .{ .kind = .reject_before_seal, .detail = "checked byte multiplication plus one exact acquisition reject overflow or OOM before activation", }, .risks = .{ .transitive = .{ .status = .open, .detail = "standard graph hash lookup is allocation-free in the witness but lacks a transitive allocation-closure certificate", }, .foreign = .{ .status = .excluded, .detail = "the index is process-local caller-owned memory with no operating-system or callback edge", }, }, .obligations = &.{ .{ .key = "smg_call_metrics_index_capacity_capacity_model", .role = .capacity_model }, .{ .key = "smg_call_metrics_index_capacity_overload", .role = .overload }, .{ .key = "smg_call_metrics_index_oom", .role = .overload }, .{ .key = "smg_call_metrics_index_sealed_transitive_risk", .role = .transitive_risk }, .{ .key = "smg_call_metrics_index_sealed_foreign_risk", .role = .foreign_risk }, }, }, .bindings = .{ .owner = @This(), .seal = .{ .family = alloc_phase.capacity.selector(@This().activate), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, .teardown = .{ .family = alloc_phase.capacity.selector(@This().deinit), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, }, }; phase: alloc_phase.capacity.Phase, capacity: Capacity, counts: []CallMetricsCounts, failures: []core.ResolutionFailure, pub fn init(allocator: std.mem.Allocator, limits: Limits) InitError!CallMetricsIndex { const capacity = try Capacity.derive(limits); const counts = if (capacity.counts == 0) @constCast((&[_]CallMetricsCounts{})[0..]) else try allocator.alloc(CallMetricsCounts, capacity.counts); errdefer if (counts.len != 0) allocator.free(counts); const failures = if (capacity.failures == 0) @constCast((&[_]core.ResolutionFailure{})[0..]) else try allocator.alloc(core.ResolutionFailure, capacity.failures); return .{ .phase = .initialization, .capacity = capacity, .counts = counts, .failures = failures, }; } pub fn fill( self: *CallMetricsIndex, graph: *const graph_mod.Graph, measured_files: []const []const u8, failures: []const core.ResolutionFailure, ) void { std.debug.assert(self.phase == .initialization); std.debug.assert(graph.nodes.items.len == self.capacity.counts); std.debug.assert(failures.len == self.failures.len); @memset(self.counts, .{}); for (graph.nodes.items, 0..) |node, index| { const callable = std.mem.eql( u8, node.type, model.NodeType.function, ) or std.mem.eql(u8, node.type, model.NodeType.method); self.counts[index].resolution_known = callable and graph_mod.isScan(node.metadata) and fileMeasured(measured_files, node.file); } for (graph.edges.items) |edge| { if (!std.mem.eql(u8, edge.rel, model.RelType.calls)) continue; const source = graph.node_index.get(edge.source).?; const target = graph.node_index.get(edge.target).?; self.counts[source].fan_out += 1; self.counts[target].fan_in += 1; } @memcpy(self.failures, failures); std.mem.sort( core.ResolutionFailure, self.failures, {}, failureLess, ); for (self.failures, 0..) |failure, index| { if (index != 0 and sameFailure(self.failures[index - 1], failure)) { continue; } const source = graph.node_index.get(failure.source) orelse continue; switch (failure.bucket) { .unresolved => self.counts[source].unresolved_targets += 1, .external => self.counts[source].external_targets += 1, } self.counts[source].resolution_known = true; } } pub fn activate(self: *CallMetricsIndex) void { std.debug.assert(self.phase == .initialization); self.phase = .steady; } pub fn get(self: *const CallMetricsIndex, node: usize) CallMetricsCounts { std.debug.assert(self.phase == .steady); std.debug.assert(node < self.counts.len); return self.counts[node]; } pub fn deinit(self: *CallMetricsIndex, allocator: std.mem.Allocator) void { std.debug.assert(self.phase != .teardown); self.phase = .teardown; if (self.counts.len != 0) allocator.free(self.counts); if (self.failures.len != 0) allocator.free(self.failures); self.* = undefined; }};comptime { alloc_phase.capacity.requireAllocatorExactOwnerShape(CallMetricsIndex);}pub fn updateCallMetrics( allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, measured_files: []const []const u8, failures: []const core.ResolutionFailure,) !void { var counts = try CallMetricsIndex.init(scratch, .{ .nodes = graph.nodes.items.len, .failures = failures.len, }); defer counts.deinit(scratch); counts.fill(graph, measured_files, failures); counts.activate(); var parse_arena = std.heap.ArenaAllocator.init(scratch); defer parse_arena.deinit(); for (graph.nodes.items, 0..) |*node, node_index| { const existing = model.pairValue(node.metadata, "metrics"); if (!std.mem.eql(u8, node.type, model.NodeType.function) and !std.mem.eql(u8, node.type, model.NodeType.method)) { if (existing) |raw| { const value = try allocator.dupe(u8, raw); std.debug.assert(model.replaceOwnedPairValue(node.metadata, "metrics", value, true)); } continue; } _ = parse_arena.reset(.retain_capacity); var metrics = if (existing) |raw| parseMetrics(parse_arena.allocator(), raw) catch FunctionMetrics{} else FunctionMetrics{ .cyclomatic_complexity = 0 }; const call_counts = counts.get(node_index); if (call_counts.resolution_known) { metrics.unresolved_call_targets = @intCast( call_counts.unresolved_targets, ); metrics.external_call_targets = @intCast( call_counts.external_targets, ); } const fan_in = call_counts.fan_in; const fan_out = call_counts.fan_out; if (existing != null) { const value = try metricsJsonWithCalls( allocator, metrics, @intCast(fan_in), @intCast(fan_out), ); std.debug.assert(model.replaceOwnedPairValue(node.metadata, "metrics", value, true)); } else { const pairs = try allocator.alloc(model.Pair, node.metadata.len + 1); @memcpy(pairs[0..node.metadata.len], node.metadata); pairs[node.metadata.len] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try callMetricsJson( allocator, @intCast(fan_in), @intCast(fan_out), metrics, ), .json = true, }; node.metadata = pairs; } }}fn parseMetrics(allocator: std.mem.Allocator, raw: []const u8) !FunctionMetrics { const parsed = try std.json.parseFromSlice(std.json.Value, allocator, raw, .{}); defer parsed.deinit(); const object = switch (parsed.value) { .object => |object| object, else => return error.InvalidMetrics, }; return .{ .cyclomatic_complexity = jsonInt(object, "cyclomatic_complexity"), .cognitive_complexity = jsonInt(object, "cognitive_complexity"), .max_nesting_depth = jsonInt(object, "max_nesting_depth"), .lines_of_code = jsonInt(object, "lines_of_code"), .parameter_count = jsonInt(object, "parameter_count"), .return_count = jsonInt(object, "return_count"), .unresolved_call_targets = jsonOptionalInt( object, "unresolved_call_targets", ), .external_call_targets = jsonOptionalInt( object, "external_call_targets", ), };}fn jsonOptionalInt(object: std.json.ObjectMap, key: []const u8) ?i64 { const value = object.get(key) orelse return null; const integer = switch (value) { .integer => |integer| integer, else => return null, }; if (integer < 0) return null; return integer;}fn jsonInt(object: std.json.ObjectMap, key: []const u8) i64 { const value = object.get(key) orelse return 0; return switch (value) { .integer => |integer| integer, .float => |float| @intFromFloat(float), else => 0, };}test "call metrics index capacity matches an independent node storage model" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_capacity_capacity_model"), null, null, null, null, null, null, ); } comptime { @stardustClaim( @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_capacity_overload"), null, null, null, null, null, null, ); } for (0..64) |nodes| { const failures = nodes / 2; const capacity = try CallMetricsIndex.Capacity.derive(.{ .nodes = nodes, .failures = failures, }); try std.testing.expectEqual(nodes, capacity.counts); try std.testing.expectEqual(failures, capacity.failures); try std.testing.expectEqual( nodes * @sizeOf(CallMetricsCounts) + failures * @sizeOf(core.ResolutionFailure), capacity.bytes, ); } try std.testing.expectError( error.CapacityOverflow, CallMetricsIndex.Capacity.derive(.{ .nodes = std.math.maxInt(usize), .failures = 0, }), );}fn checkCallMetricsIndexInitAllocationFailures(allocator: std.mem.Allocator) !void { var index = try CallMetricsIndex.init(allocator, .{ .nodes = 3, .failures = 2, }); index.deinit(allocator);}test "call metrics index initialization cleans allocation failure and retries" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_oom"), null, null, null, null, null, null, ); } try std.testing.checkAllAllocationFailures( std.testing.allocator, checkCallMetricsIndexInitAllocationFailures, .{}, ); var index = try CallMetricsIndex.init(std.testing.allocator, .{ .nodes = 1, .failures = 0, }); defer index.deinit(std.testing.allocator); index.activate(); try std.testing.expectEqual(alloc_phase.capacity.Phase.steady, index.phase);}test "call metrics index fills and serves exact call counts while sealed" { comptime { @stardustClaim( @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_sealed_transitive_risk"), null, null, null, null, null, null, ); } comptime { @stardustClaim( @import("alloc_phase").capacity.witness(CallMetricsIndex, "smg_call_metrics_index_sealed_foreign_risk"), null, null, null, null, null, null, ); } var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); 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.run", .type = model.NodeType.function }); try graph_mod.addNode(&graph, .{ .name = "app.help", .type = model.NodeType.function }); try graph_mod.addEdge(&graph, .{ .source = "app", .rel = model.RelType.contains, .target = "app.run" }); try graph_mod.addEdge(&graph, .{ .source = "app.run", .rel = model.RelType.calls, .target = "app.help" }); var phase_allocator = try alloc_phase.SealedPhaseAllocator.init(std.testing.allocator); var index = try CallMetricsIndex.init( phase_allocator.initializationAllocator(), .{ .nodes = graph.nodes.items.len, .failures = 0, }, ); defer { if (phase_allocator.phase() == .initialization) phase_allocator.abortInitialization(); if (phase_allocator.phase() == .steady) phase_allocator.beginTeardown(); index.deinit(phase_allocator.teardownAllocator()); phase_allocator.deinit(); } phase_allocator.seal(); index.fill(&graph, &.{}, &.{}); index.activate(); try std.testing.expectEqual(@as(usize, 0), index.get(0).fan_in); try std.testing.expectEqual(@as(usize, 1), index.get(1).fan_out); try std.testing.expectEqual(@as(usize, 1), index.get(2).fan_in);}test "call metrics replace the owned metadata value without replacing pair storage" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); var graph = graph_mod.init(allocator); const metadata = try allocator.alloc(model.Pair, 2); metadata[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try allocator.dupe(u8, "{\"cyclomatic_complexity\":2,\"cognitive_complexity\":1,\"max_nesting_depth\":1,\"lines_of_code\":3,\"parameter_count\":0,\"return_count\":1,\"fan_in\":0,\"fan_out\":0}"), .json = true }; metadata[1] = .{ .key = try allocator.dupe(u8, "source"), .value = try allocator.dupe(u8, "scan") }; try graph_mod.addNode(&graph, .{ .name = "app.run", .type = model.NodeType.function, .metadata = metadata }); const metadata_pointer = graph.nodes.items[0].metadata.ptr; try updateCallMetrics( allocator, std.testing.allocator, &graph, &.{}, &.{}, ); try std.testing.expectEqual(metadata_pointer, graph.nodes.items[0].metadata.ptr); const metrics = model.pairValue(graph.nodes.items[0].metadata, "metrics").?; try std.testing.expect(std.mem.indexOf(u8, metrics, "\"fan_in\":0") != null); try std.testing.expect(std.mem.indexOf(u8, metrics, "\"fan_out\":0") != null);}test "resolution metrics keep missing and malformed evidence unknown" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const allocator = arena.allocator(); const missing = try parseMetrics(allocator, "{}"); try std.testing.expectEqual( @as(?i64, null), missing.unresolved_call_targets, ); try std.testing.expectEqual( @as(?i64, null), missing.external_call_targets, ); const malformed = try parseMetrics( allocator, "{\"unresolved_call_targets\":\"2\",\"external_call_targets\":-1}", ); try std.testing.expectEqual( @as(?i64, null), malformed.unresolved_call_targets, ); try std.testing.expectEqual( @as(?i64, null), malformed.external_call_targets, );}test "call metrics finalization transfers metrics retained by a non-callable node" { var graph_arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer graph_arena.deinit(); const allocator = graph_arena.allocator(); var graph = graph_mod.init(allocator); var input_arena = std.heap.ArenaAllocator.init(std.testing.allocator); var input_live = true; defer if (input_live) input_arena.deinit(); const input = input_arena.allocator(); const metadata = try allocator.alloc(model.Pair, 1); metadata[0] = .{ .key = try allocator.dupe(u8, "metrics"), .value = try input.dupe(u8, "{\"lines_of_code\":6}"), .json = true }; try graph_mod.addNode(&graph, .{ .name = "app.tool", .type = model.NodeType.module, .metadata = metadata }); try updateCallMetrics( allocator, std.testing.allocator, &graph, &.{}, &.{}, ); input_arena.deinit(); input_live = false; try std.testing.expectEqualStrings("{\"lines_of_code\":6}", model.pairValue(graph.nodes.items[0].metadata, "metrics").?);}Source: tools/smg/src/scan/root.zig:6
zig
pub const scan_metrics = @import("metrics.zig");Complete caller list for scan.scan_metrics.braceDelta
10 direct callers.
tools.smg.src.scan.metrics.openBraceTextMetricDepth[function] — private; no exact target attools/smg/src/scan/metrics.zig:291in nearest public ownertiny.smg.scan.scan_metricstools.smg.src.scan.pipeline.scan.continueSkippedCFunction[function] — private; no exact target attools/smg/src/scan/pipeline/scan.zig:3671in nearest public ownertools.smg.src.scan.pipeline.scantools.smg.src.scan.pipeline.scan.finishCLine[function] — private; no exact target attools/smg/src/scan/pipeline/scan.zig:3794in nearest public ownertools.smg.src.scan.pipeline.scantools.smg.src.scan.pipeline.scan.finishCStructuralLine[function] — private; no exact target attools/smg/src/scan/pipeline/scan.zig:3807in nearest public ownertools.smg.src.scan.pipeline.scantools.smg.src.scan.pipeline.scan.scanCDefineLine[function] — private; no exact target attools/smg/src/scan/pipeline/scan.zig:3693in nearest public ownertools.smg.src.scan.pipeline.scantools.smg.src.scan.script.continueArrowSkip[function] — private; no exact target attools/smg/src/scan/script.zig:135in nearest public ownertiny.smg.scan.scripttools.smg.src.scan.script.finishLine[function] — private; no exact target attools/smg/src/scan/script.zig:208in nearest public ownertiny.smg.scan.scripttools.smg.src.scan.script.scanClassDeclarationLine[function] — private; no exact target attools/smg/src/scan/script.zig:178in nearest public ownertiny.smg.scan.scripttools.smg.src.scan.script.scanClassLine[function] — private; no exact target attools/smg/src/scan/script.zig:148in nearest public ownertiny.smg.scan.scripttools.smg.src.scan.script.scanInterfaceLine[function] — private; no exact target attools/smg/src/scan/script.zig:172in nearest public ownertiny.smg.scan.script
Complete caller list for scan.scan_metrics.pythonCommentLine
7 direct callers.
tools.smg.src.scan.metrics.updatePythonTextMetricLine[function] — private; no exact target attools/smg/src/scan/metrics.zig:276in nearest public ownertiny.smg.scan.scan_metricstools.smg.src.scan.python.blockEnd[function] — private; no exact target attools/smg/src/scan/python.zig:433in nearest public ownertiny.smg.scan.pythontools.smg.src.scan.python.bodyLineAction[function] — private; no exact target attools/smg/src/scan/python.zig:212in nearest public ownertiny.smg.scan.pythontools.smg.src.scan.python.docstring[function] — private; no exact target attools/smg/src/scan/python.zig:506in nearest public ownertiny.smg.scan.pythontools.smg.src.scan.python.extractCallsText[function] — private; no exact target attools/smg/src/scan/python.zig:363in nearest public ownertiny.smg.scan.pythontools.smg.src.scan.python.extractDynamicImportsText[function] — private; no exact target attools/smg/src/scan/python.zig:412in nearest public ownertiny.smg.scan.pythontools.smg.src.scan.python.extractImportsText[function] — private; no exact target attools/smg/src/scan/python.zig:112in nearest public ownertiny.smg.scan.python
Complete caller list for scan.scan_metrics.updateCallMetrics
38 direct callers.
tools.smg.src.scan.metrics.test_call_metrics_finalization_transfers_metrics_retained_by_a_non-callable_node[function] — test; no exact target attools/smg/src/scan/metrics.zig:1328in nearest public ownertiny.smg.scan.scan_metricstools.smg.src.scan.metrics.test_call_metrics_replace_the_owned_metadata_value_without_replacing_pair_storage[function] — test; no exact target attools/smg/src/scan/metrics.zig:1276in nearest public ownertiny.smg.scan.scan_metricstools.smg.src.scan.pipeline.scan.finishCachedProject[function] — private; no exact target attools/smg/src/scan/pipeline/scan.zig:1105in nearest public ownertools.smg.src.scan.pipeline.scantiny.smg.scan.scanPrepared[function] attools/smg/src/scan/pipeline/scan.zig:813tools.smg.src.scan.test.test_javascript_fallback_ignores_constructor_expressions[function] — test; no exact target attools/smg/src/scan/test.zig:1450in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_branches[function] — test; no exact target attools/smg/src/scan/test.zig:1894in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_do_loops[function] — test; no exact target attools/smg/src/scan/test.zig:2041in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_else_branches[function] — test; no exact target attools/smg/src/scan/test.zig:1915in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_for_in_loops[function] — test; no exact target attools/smg/src/scan/test.zig:1999in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_for_loops[function] — test; no exact target attools/smg/src/scan/test.zig:1978in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_for_of_loops[function] — test; no exact target attools/smg/src/scan/test.zig:2020in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_logical_expressions[function] — test; no exact target attools/smg/src/scan/test.zig:1873in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_switch_default_clauses[function] — test; no exact target attools/smg/src/scan/test.zig:2125in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_switch_statements[function] — test; no exact target attools/smg/src/scan/test.zig:2104in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_ternary_expressions[function] — test; no exact target attools/smg/src/scan/test.zig:1852in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_try_catch_statements[function] — test; no exact target attools/smg/src/scan/test.zig:2062in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_counts_compact_method_while_loops[function] — test; no exact target attools/smg/src/scan/test.zig:1936in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_async_awaited_calls[function] — test; no exact target attools/smg/src/scan/test.zig:1791in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_concise_arrow_calls[function] — test; no exact target attools/smg/src/scan/test.zig:1556in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_default_function_values[function] — test; no exact target attools/smg/src/scan/test.zig:1637in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_export_declarations[function] — test; no exact target attools/smg/src/scan/test.zig:1607in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_lexical_initializer_calls[function] — test; no exact target attools/smg/src/scan/test.zig:1831in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_method_calls[function] — test; no exact target attools/smg/src/scan/test.zig:1684in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_method_calls_with_arguments[function] — test; no exact target attools/smg/src/scan/test.zig:1730in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_multi-root_helper_calls[function] — test; no exact target attools/smg/src/scan/test.zig:1707in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_extracts_compact_return_method_calls[function] — test; no exact target attools/smg/src/scan/test.zig:1773in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_handles_compact_method_break_statements[function] — test; no exact target attools/smg/src/scan/test.zig:1957in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_handles_compact_method_throw_statements[function] — test; no exact target attools/smg/src/scan/test.zig:2083in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_ignores_compact_method_new_expressions[function] — test; no exact target attools/smg/src/scan/test.zig:1810in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_inserts_compact_class_methods[function] — test; no exact target attools/smg/src/scan/test.zig:1475in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_inserts_compact_let_and_var_functions[function] — test; no exact target attools/smg/src/scan/test.zig:1579in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_inserts_compact_lexical_functions[function] — test; no exact target attools/smg/src/scan/test.zig:1536in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_inserts_compact_static_class_methods[function] — test; no exact target attools/smg/src/scan/test.zig:1513in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_preserves_compact_nested_member_call_arguments[function] — test; no exact target attools/smg/src/scan/test.zig:1752in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_javascript_tree_scan_skips_compact_class_fields[function] — test; no exact target attools/smg/src/scan/test.zig:1495in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_scan_graph_retains_no_input_path_or_source_views[function] — test; no exact target attools/smg/src/scan/test.zig:799in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_typescript_tree_scan_resolves_compact_interface_implements[function] — test; no exact target attools/smg/src/scan/test.zig:1659in nearest public ownertools.smg.src.scan.testtools.smg.src.scan.test.test_typescript_tree_scan_skips_compact_type_aliases[function] — test; no exact target attools/smg/src/scan/test.zig:2326in nearest public ownertools.smg.src.scan.test
Complete call list for scan.scan_metrics.updateCallMetrics
10 direct calls.
tiny.smg.model.pairValue[function] attools/smg/src/model.zig:139tiny.smg.model.replaceOwnedPairValue[function] attools/smg/src/model.zig:144tiny.smg.scan.CallMetricsIndex.activate[method] attools/smg/src/scan/metrics.zig:1002tiny.smg.scan.CallMetricsIndex.deinit[method] attools/smg/src/scan/metrics.zig:1013tiny.smg.scan.CallMetricsIndex.fill[method] attools/smg/src/scan/metrics.zig:955tiny.smg.scan.CallMetricsIndex.get[method] attools/smg/src/scan/metrics.zig:1007tiny.smg.scan.CallMetricsIndex.init[function] attools/smg/src/scan/metrics.zig:936tiny.smg.scan.scan_metrics.callMetricsJson[function] attools/smg/src/scan/metrics.zig:658tools.smg.src.scan.metrics.metricsJsonWithCalls[function] — private; no exact target attools/smg/src/scan/metrics.zig:649in nearest public ownertiny.smg.scan.scan_metricstools.smg.src.scan.metrics.parseMetrics[function] — private; no exact target attools/smg/src/scan/metrics.zig:1093in nearest public ownertiny.smg.scan.scan_metrics
Audit
| Definitions | 30 |
|---|---|
| Public names | 41 |
| Members | 21 |
| Version | 26.7.0 |
| Revision | daab053ee433 |