Skip to documentation
SLOP

tiny.smg.scan.script

Reference tiny.smg scan script

Defined in scan.

API (3)

Actions

Public operations.

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

Source

Called byCallsNo direct callsprivate; no linktools.smg.src.scan.pipeline.scanscanBraceLanguagescan.scriptlanguage
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linktools.smg.src.scan.pipeline.scanscanBraceLanguageprivate; no linktools.smg.src.scan.scriptenhanceMetadataFromTreeprivate; no linktools.smg.src.scan.scriptscanLinescan.scriptscan
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsprivate; no linktools.smg.src.scan.scriptenhanceMetadataFromTreetest; no linktools.smg.src.scan.testtest: tree parser gates current compa...test; no linktools.smg.src.scan.testtest: tree parser gates minified and ...scan.scripttreeParseCandidate
Static calls · unresolved targets: 0 · external targets: 1.

Source: tools/smg/src/scan/root.zig:11

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

Source: tools/smg/src/scan/script.zig

zig
const std = @import("std");const smg = @import("../root.zig");const graph_mod = smg.graph;const lines_mod = smg.lines;const model = smg.model;const scan_root = smg.scan;const text = smg.text;const tree = smg.tree;const core = scan_root.core;const metrics = scan_root.scan_metrics;const scan_files = scan_root.scan_files;const CallTarget = core.CallTarget;const SourceLines = lines_mod.SourceLines;const Stats = core.Stats;const braceDelta = metrics.braceDelta;const identByte = metrics.identByte;const javascriptFunctionMetadata = metrics.javascriptFunctionMetadata;const scriptSpanMetadata = metrics.scriptSpanMetadata;const textFunctionMetadata = metrics.textFunctionMetadata;const textSpanMetadata = metrics.textSpanMetadata;const ScriptFunctionScope = struct {    name: []const u8,    start_line: i64,    start_depth: i64,    class_name: ?[]const u8 = null,};const ScriptArrowSkipKind = enum {    brace,    paren,};const ScriptArrowSkip = struct {    kind: ScriptArrowSkipKind,    brace_depth: i64,    paren_depth: i64,};const ScriptScanContext = struct {    allocator: std.mem.Allocator,    scratch: std.mem.Allocator,    graph: *graph_mod.Graph,    module: []const u8,    path: []const u8,    lang: []const u8,    stats: *Stats,};const ScriptScanState = struct {    depth: i64 = 0,    paren_depth: i64 = 0,    current_class: ?ScriptFunctionScope = null,    current_interface: ?ScriptFunctionScope = null,    current_function: ?ScriptFunctionScope = null,    skipped_arrow: ?ScriptArrowSkip = null,};const ScriptEnhanceContext = struct {    allocator: std.mem.Allocator,    scratch: std.mem.Allocator,    graph: *graph_mod.Graph,    module: []const u8,    path: []const u8,    source: []const u8,    stats: *Stats,};const ScriptBody = struct {    fragment: []const u8,    braced: bool,};const ScriptMemberObject = struct {    value: []const u8,    start: usize,};const ScriptDecl = struct {    name: []const u8,    value: []const u8,};pub fn language(lang: []const u8) bool {    return std.mem.eql(u8, lang, "javascript") or std.mem.eql(u8, lang, "typescript");}pub fn treeParseCandidate(source: []const u8) bool {    return core.compactParseCandidateAny(source, &.{ "import ", "const ", "let ", "var ", "function ", "async function ", "class ", "interface ", "type ", "export " });}pub fn scan(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, source: []const u8, source_lines: SourceLines, lang: []const u8, stats: *Stats, limits: smg.ParserLimits) !void {    const context: ScriptScanContext = .{ .allocator = allocator, .scratch = scratch, .graph = graph, .module = module, .path = path, .lang = lang, .stats = stats };    var state: ScriptScanState = .{};    var lines = std.mem.splitScalar(u8, source, '\n');    var line_no: i64 = 0;    while (lines.next()) |raw_line| {        line_no += 1;        try scanLine(context, &state, raw_line, line_no);    }    try enhanceMetadataFromTree(        allocator,        scratch,        graph,        module,        path,        source,        source_lines,        lang,        stats,        limits,    );}fn scanLine(context: ScriptScanContext, state: *ScriptScanState, raw_line: []const u8, line_no: i64) !void {    const trimmed = text.trim(raw_line);    if (trimmed.len == 0) return;    const depth_before = state.depth;    const paren_depth_before = state.paren_depth;    if (state.skipped_arrow != null) return try continueArrowSkip(context, state, trimmed, line_no);    if (state.current_function) |function_scope| {        state.skipped_arrow = try extractCallsOutsideNestedArrows(context.allocator, function_scope.name, trimmed, depth_before, paren_depth_before, context.stats, function_scope.class_name);    } else if (state.current_class != null and depth_before > state.current_class.?.start_depth) {        try scanClassLine(context, state, trimmed, line_no, depth_before, paren_depth_before);    } else if (depth_before == 0) {        try scanTopLevelLine(context, state, trimmed, line_no, depth_before, paren_depth_before);    }    try finishLine(context, state, trimmed, line_no);}fn continueArrowSkip(context: ScriptScanContext, state: *ScriptScanState, trimmed: []const u8, line_no: i64) !void {    state.depth += braceDelta(trimmed);    state.paren_depth += parenDelta(trimmed);    const skip = state.skipped_arrow.?;    const skip_done = switch (skip.kind) {        .brace => state.depth <= skip.brace_depth,        .paren => state.paren_depth <= skip.paren_depth,    };    if (skip_done) state.skipped_arrow = null;    try closeScopes(context, state, line_no);    clampDepths(state);}fn scanClassLine(context: ScriptScanContext, state: *ScriptScanState, trimmed: []const u8, line_no: i64, depth_before: i64, paren_depth_before: i64) !void {    const class_name = state.current_class.?.name;    const name = methodName(trimmed) orelse return;    const full = try addMethodNode(context.allocator, context.graph, class_name, context.path, name, line_no, context.stats);    if (functionBody(trimmed)) |body| {        state.skipped_arrow = try extractCallsOutsideNestedArrows(context.allocator, full, body.fragment, depth_before, paren_depth_before, context.stats, class_name);        if (body.braced and braceDelta(trimmed) > 0) state.current_function = .{ .name = full, .start_line = line_no, .start_depth = depth_before, .class_name = class_name };    }}fn scanTopLevelLine(context: ScriptScanContext, state: *ScriptScanState, trimmed: []const u8, line_no: i64, depth_before: i64, paren_depth_before: i64) !void {    try scanImportLine(context, trimmed);    try scanInterfaceLine(context, state, trimmed, line_no, depth_before);    try scanClassDeclarationLine(context, state, trimmed, line_no, depth_before);    try scanConstantLine(context, trimmed, line_no);    try scanFunctionLine(context, state, trimmed, line_no, depth_before, paren_depth_before);}fn scanImportLine(context: ScriptScanContext, trimmed: []const u8) !void {    const target = jsImport(trimmed) orelse return;    const resolved_target = try importTarget(context.allocator, context.scratch, target, context.module, context.path) orelse return;    try core.addImportEdge(context.allocator, context.graph, context.module, resolved_target, &.{}, context.stats);}fn scanInterfaceLine(context: ScriptScanContext, state: *ScriptScanState, trimmed: []const u8, line_no: i64, depth_before: i64) !void {    const name = interfaceName(trimmed) orelse return;    const full = try addInterfaceNode(context.allocator, context.graph, context.module, context.path, name, line_no, context.stats);    if (braceDelta(trimmed) > 0) state.current_interface = .{ .name = full, .start_line = line_no, .start_depth = depth_before };}fn scanClassDeclarationLine(context: ScriptScanContext, state: *ScriptScanState, trimmed: []const u8, line_no: i64, depth_before: i64) !void {    const name = className(trimmed) orelse return;    const full = try addClassNode(context.allocator, context.graph, context.module, context.path, name, line_no, context.stats);    try addHeritageEdges(context.allocator, context.stats, full, trimmed);    if (braceDelta(trimmed) > 0) state.current_class = .{ .name = full, .start_line = line_no, .start_depth = depth_before };}fn scanConstantLine(context: ScriptScanContext, trimmed: []const u8, line_no: i64) !void {    const name = constantName(trimmed) orelse return;    try addConstantNode(context.allocator, context.graph, context.module, context.path, name, line_no, context.stats);}fn addConstantNode(allocator: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, name: []const u8, line_no: i64, stats: *Stats) !void {    const full = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module, name });    const meta = try model.sourcePair(allocator, "scan");    try graph_mod.addNode(graph, .{ .name = full, .type = model.NodeType.constant, .file = path, .line = line_no, .end_line = line_no, .metadata = meta });    try core.addScanEdge(graph, stats, .{ .source = module, .rel = model.RelType.contains, .target = full, .metadata = meta });    stats.nodes_added += 1;    try core.countType(stats, allocator, model.NodeType.constant);}fn scanFunctionLine(context: ScriptScanContext, state: *ScriptScanState, trimmed: []const u8, line_no: i64, depth_before: i64, paren_depth_before: i64) !void {    const name = functionName(trimmed) orelse return;    const full = try addFunctionNode(context.allocator, context.graph, context.module, context.path, name, line_no, context.stats);    if (functionBody(trimmed)) |body| {        state.skipped_arrow = try extractCallsOutsideNestedArrows(context.allocator, full, body.fragment, depth_before, paren_depth_before, context.stats, null);        if (body.braced) state.current_function = .{ .name = full, .start_line = line_no, .start_depth = depth_before };    }}fn finishLine(context: ScriptScanContext, state: *ScriptScanState, trimmed: []const u8, line_no: i64) !void {    state.depth += braceDelta(trimmed);    state.paren_depth += parenDelta(trimmed);    try closeScopes(context, state, line_no);    clampDepths(state);}fn closeScopes(context: ScriptScanContext, state: *ScriptScanState, line_no: i64) !void {    if (state.current_function) |function_scope| {        if (state.depth <= function_scope.start_depth) {            try updateFunctionNodeEnd(context.allocator, context.graph, function_scope.name, context.path, function_scope.start_line, line_no);            state.current_function = null;        }    }    if (state.current_class) |class_scope| {        if (state.depth <= class_scope.start_depth) {            try updateNodeEnd(context.allocator, context.graph, class_scope.name, context.path, class_scope.start_line, line_no);            state.current_class = null;        }    }    if (state.current_interface) |interface_scope| {        if (state.depth <= interface_scope.start_depth) {            try updateNodeEnd(context.allocator, context.graph, interface_scope.name, context.path, interface_scope.start_line, line_no);            state.current_interface = null;        }    }}fn clampDepths(state: *ScriptScanState) void {    if (state.depth < 0) state.depth = 0;    if (state.paren_depth < 0) state.paren_depth = 0;}fn addFunctionNode(allocator: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, name: []const u8, line_no: i64, stats: *Stats) ![]const u8 {    const full = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module, name });    const meta = try model.sourcePair(allocator, "scan");    try graph_mod.addNode(graph, .{ .name = full, .type = model.NodeType.function, .file = path, .line = line_no, .end_line = line_no, .metadata = meta });    try core.addScanEdge(graph, stats, .{ .source = module, .rel = model.RelType.contains, .target = full, .metadata = meta });    stats.nodes_added += 1;    try core.countType(stats, allocator, model.NodeType.function);    return full;}fn addClassNode(allocator: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, name: []const u8, line_no: i64, stats: *Stats) ![]const u8 {    const full = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module, name });    const meta = try model.sourcePair(allocator, "scan");    try graph_mod.addNode(graph, .{ .name = full, .type = model.NodeType.class, .file = path, .line = line_no, .end_line = line_no, .metadata = meta });    try core.addScanEdge(graph, stats, .{ .source = module, .rel = model.RelType.contains, .target = full, .metadata = meta });    stats.nodes_added += 1;    try core.countType(stats, allocator, model.NodeType.class);    return full;}fn addInterfaceNode(allocator: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, name: []const u8, line_no: i64, stats: *Stats) ![]const u8 {    const full = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module, name });    const meta = try model.sourcePair(allocator, "scan");    try graph_mod.addNode(graph, .{ .name = full, .type = model.NodeType.interface, .file = path, .line = line_no, .end_line = line_no, .metadata = meta });    try core.addScanEdge(graph, stats, .{ .source = module, .rel = model.RelType.contains, .target = full, .metadata = meta });    stats.nodes_added += 1;    try core.countType(stats, allocator, model.NodeType.interface);    return full;}fn addMethodNode(allocator: std.mem.Allocator, graph: *graph_mod.Graph, class_name: []const u8, path: []const u8, name: []const u8, line_no: i64, stats: *Stats) ![]const u8 {    const full = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ class_name, name });    const meta = try model.sourcePair(allocator, "scan");    try graph_mod.addNode(graph, .{ .name = full, .type = model.NodeType.method, .file = path, .line = line_no, .end_line = line_no, .metadata = meta });    try core.addScanEdge(graph, stats, .{ .source = class_name, .rel = model.RelType.contains, .target = full, .metadata = meta });    stats.nodes_added += 1;    try core.countType(stats, allocator, model.NodeType.method);    return full;}fn updateFunctionNodeEnd(allocator: std.mem.Allocator, graph: *graph_mod.Graph, name: []const u8, path: []const u8, line: i64, end_line: i64) !void {    try updateNodeEnd(allocator, graph, name, path, line, end_line);}fn updateNodeEnd(allocator: std.mem.Allocator, graph: *graph_mod.Graph, name: []const u8, path: []const u8, line: i64, end_line: i64) !void {    _ = allocator;    const existing = graph_mod.getNode(graph, name) orelse return;    try graph_mod.addNode(graph, .{ .name = name, .type = existing.type, .file = path, .line = line, .end_line = end_line, .metadata = existing.metadata });}fn enhanceMetadataFromTree(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, source: []const u8, source_lines: SourceLines, lang: []const u8, stats: *Stats, limits: smg.ParserLimits) !void {    if (!treeParseCandidate(source)) return try enhanceMetadataFromText(allocator, graph, path, source_lines, stats);    const parsed: ?tree.Parsed = if (std.mem.eql(u8, lang, "javascript"))        try tree.parseJavaScript(scratch, source, limits)    else if (std.mem.eql(u8, lang, "typescript") and std.mem.endsWith(u8, path, ".tsx"))        try tree.parseTsx(scratch, source, limits)    else if (std.mem.eql(u8, lang, "typescript"))        try tree.parseTypeScript(scratch, source, limits)    else        return;    const parsed_value = parsed orelse return try enhanceMetadataFromText(allocator, graph, path, source_lines, stats);    defer tree.deinit(parsed_value);    try enhanceBody(allocator, scratch, graph, module, path, source, tree.root(parsed_value), stats);}fn enhanceMetadataFromText(allocator: std.mem.Allocator, graph: *graph_mod.Graph, path: []const u8, source_lines: SourceLines, stats: *Stats) !void {    for (graph.nodes.items) |*node| {        const file = node.file orelse continue;        if (!std.mem.eql(u8, file, path)) continue;        const line = node.line orelse continue;        const end_line = node.end_line orelse line;        if (std.mem.eql(u8, node.type, model.NodeType.function) or std.mem.eql(u8, node.type, model.NodeType.method)) {            node.metadata = try model.mergePairs(allocator, node.metadata, try textFunctionMetadata(allocator, stats.metricInputAllocator(allocator), source_lines, line, end_line, .javascript));        } else if (std.mem.eql(u8, node.type, model.NodeType.class)) {            node.metadata = try model.mergePairs(allocator, node.metadata, try textSpanMetadata(allocator, source_lines, line, end_line, null));        }    }}fn enhanceBody(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, source: []const u8, node: tree.Node, stats: *Stats) !void {    const context: ScriptEnhanceContext = .{ .allocator = allocator, .scratch = scratch, .graph = graph, .module = module, .path = path, .source = source, .stats = stats };    const children = tree.childCount(node);    for (0..children) |raw_index| {        const child_node = tree.child(node, @intCast(raw_index));        try enhanceChild(context, child_node);    }}fn enhanceChild(context: ScriptEnhanceContext, node: tree.Node) anyerror!void {    const node_kind = tree.kind(node);    if (try enhanceImportChild(context, node_kind, node)) return;    if (try enhanceNamedDeclarationChild(context, node_kind, node)) return;    if (try enhanceVariableChild(context, node_kind, node)) return;    if (std.mem.eql(u8, node_kind, "export_statement")) {        try enhanceImport(context.allocator, context.scratch, context.graph, context.module, context.path, context.source, node, context.stats);        const children = tree.childCount(node);        for (0..children) |raw_index| {            const child_node = tree.child(node, @intCast(raw_index));            try enhanceChild(context, child_node);        }        return;    }    try enhanceDefaultFunctionChild(context, node_kind, node);}fn enhanceImportChild(context: ScriptEnhanceContext, node_kind: []const u8, node: tree.Node) !bool {    if (!std.mem.eql(u8, node_kind, "import_statement")) return false;    try enhanceImport(context.allocator, context.scratch, context.graph, context.module, context.path, context.source, node, context.stats);    return true;}fn enhanceNamedDeclarationChild(context: ScriptEnhanceContext, node_kind: []const u8, node: tree.Node) !bool {    if (std.mem.eql(u8, node_kind, "class_declaration")) {        try enhanceClassDeclaration(context, node);        return true;    }    if (std.mem.eql(u8, node_kind, "interface_declaration")) {        try enhanceNamedInterface(context, node);        return true;    }    if (std.mem.eql(u8, node_kind, "function_declaration")) {        try enhanceNamedFunction(context, node);        return true;    }    return false;}fn enhanceClassDeclaration(context: ScriptEnhanceContext, node: tree.Node) !void {    const name_node = tree.childByField(node, "name") orelse return;    const raw_name = tree.slice(context.source, name_node);    const expected_name = try std.fmt.allocPrint(context.scratch, "{s}.{s}", .{ context.module, raw_name });    const known_class = graph_mod.getNode(context.graph, expected_name) != null;    const class_name = try enhanceClassNode(context.allocator, context.scratch, context.graph, context.module, context.path, context.source, node, raw_name, context.stats);    if (!known_class) try addHeritageEdges(context.allocator, context.stats, class_name, tree.slice(context.source, node));    try enhanceClassBody(context.allocator, context.scratch, context.graph, class_name, context.path, context.source, node, context.stats);}fn enhanceNamedInterface(context: ScriptEnhanceContext, node: tree.Node) !void {    const name_node = tree.childByField(node, "name") orelse return;    try enhanceInterfaceNode(context.allocator, context.scratch, context.graph, context.module, context.path, context.source, node, tree.slice(context.source, name_node), context.stats);}fn enhanceNamedFunction(context: ScriptEnhanceContext, node: tree.Node) !void {    const name_node = tree.childByField(node, "name") orelse return;    try enhanceFunctionNode(context.allocator, context.scratch, context.graph, context.module, context.path, context.source, node, tree.slice(context.source, name_node), context.stats);}fn enhanceVariableChild(context: ScriptEnhanceContext, node_kind: []const u8, node: tree.Node) !bool {    if (!std.mem.eql(u8, node_kind, "lexical_declaration") and !std.mem.eql(u8, node_kind, "variable_declaration")) return false;    try enhanceLexical(context.allocator, context.scratch, context.graph, context.module, context.path, context.source, node, context.stats);    return true;}fn enhanceDefaultFunctionChild(context: ScriptEnhanceContext, node_kind: []const u8, node: tree.Node) !void {    if (!std.mem.eql(u8, node_kind, "arrow_function") and !std.mem.eql(u8, node_kind, "function_expression")) return;    try enhanceFunctionNode(context.allocator, context.scratch, context.graph, context.module, context.path, context.source, node, "default", context.stats);}fn enhanceImport(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, source: []const u8, node: tree.Node, stats: *Stats) !void {    const children = tree.childCount(node);    for (0..children) |raw_index| {        const child_node = tree.child(node, @intCast(raw_index));        if (!std.mem.eql(u8, tree.kind(child_node), "string")) continue;        const raw = core.quoted(tree.slice(source, child_node)) orelse return;        const target = try importTarget(allocator, scratch, raw, module, path) orelse return;        if (core.edgeExists(graph.*, module, model.RelType.imports, target) or core.deferredEdgeExists(stats.*, module, model.RelType.imports, target)) return;        try core.addImportEdge(allocator, graph, module, target, &.{}, stats);        return;    }}fn enhanceClassNode(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, source: []const u8, node: tree.Node, name: []const u8, stats: *Stats) ![]const u8 {    const qualified = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module, name });    try upsertNamedNodeFromTree(allocator, graph, module, qualified, model.NodeType.class, path, tree.startLine(node), tree.endLine(node), try scriptSpanMetadata(allocator, scratch, source, node), stats);    return qualified;}fn enhanceInterfaceNode(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, source: []const u8, node: tree.Node, name: []const u8, stats: *Stats) !void {    const qualified = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module, name });    try upsertNamedNodeFromTree(allocator, graph, module, qualified, model.NodeType.interface, path, tree.startLine(node), tree.endLine(node), try scriptSpanMetadata(allocator, scratch, source, node), stats);}fn enhanceClassBody(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, class_name: []const u8, path: []const u8, source: []const u8, node: tree.Node, stats: *Stats) !void {    const body = tree.childByField(node, "body") orelse core.findChild(node, "class_body") orelse return;    const children = tree.childCount(body);    for (0..children) |raw_index| {        const child_node = tree.child(body, @intCast(raw_index));        if (!std.mem.eql(u8, tree.kind(child_node), "method_definition")) continue;        const name_node = tree.childByField(child_node, "name") orelse core.findChild(child_node, "property_identifier") orelse core.findChild(child_node, "identifier") orelse continue;        try enhanceMethodNode(allocator, scratch, graph, class_name, path, source, child_node, tree.slice(source, name_node), stats);    }}fn enhanceMethodNode(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, class_name: []const u8, path: []const u8, source: []const u8, node: tree.Node, name: []const u8, stats: *Stats) !void {    const qualified = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ class_name, name });    try upsertNamedNodeFromTree(allocator, graph, class_name, qualified, model.NodeType.method, path, tree.startLine(node), tree.endLine(node), try javascriptFunctionMetadata(allocator, scratch, stats.metricInputAllocator(allocator), source, node), stats);    try extractTreeCalls(allocator, source, qualified, node, stats, class_name);}fn enhanceLexical(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, source: []const u8, node: tree.Node, stats: *Stats) !void {    const children = tree.childCount(node);    for (0..children) |raw_index| {        const child_node = tree.child(node, @intCast(raw_index));        if (!std.mem.eql(u8, tree.kind(child_node), "variable_declarator")) continue;        const name_node = tree.childByField(child_node, "name") orelse continue;        if (!std.mem.eql(u8, tree.kind(name_node), "identifier")) continue;        const value_node = tree.childByField(child_node, "value") orelse continue;        const value_kind = tree.kind(value_node);        if (!std.mem.eql(u8, value_kind, "arrow_function") and !std.mem.eql(u8, value_kind, "function_expression")) continue;        try enhanceFunctionNode(allocator, scratch, graph, module, path, source, value_node, tree.slice(source, name_node), stats);    }}fn enhanceFunctionNode(allocator: std.mem.Allocator, scratch: std.mem.Allocator, graph: *graph_mod.Graph, module: []const u8, path: []const u8, source: []const u8, node: tree.Node, name: []const u8, stats: *Stats) !void {    const qualified = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ module, name });    try upsertNamedNodeFromTree(allocator, graph, module, qualified, model.NodeType.function, path, tree.startLine(node), tree.endLine(node), try javascriptFunctionMetadata(allocator, scratch, stats.metricInputAllocator(allocator), source, node), stats);    try extractTreeCalls(allocator, source, qualified, node, stats, null);}fn upsertNamedNodeFromTree(allocator: std.mem.Allocator, graph: *graph_mod.Graph, parent: []const u8, name: []const u8, node_type: []const u8, path: []const u8, line: i64, end_line: i64, metadata: []const model.Pair, stats: *Stats) !void {    const exists = graph.node_index.contains(name);    try graph_mod.addNode(graph, .{ .name = name, .type = node_type, .file = path, .line = line, .end_line = end_line, .metadata = metadata });    if (exists) return;    try core.addScanEdge(graph, stats, .{ .source = parent, .rel = model.RelType.contains, .target = name, .metadata = try model.sourcePair(allocator, "scan") });    stats.nodes_added += 1;    try core.countType(stats, allocator, node_type);}fn extractTreeCalls(allocator: std.mem.Allocator, source: []const u8, caller: []const u8, node: tree.Node, stats: *Stats, class_name: ?[]const u8) !void {    const body = tree.childByField(node, "body") orelse core.findChild(node, "statement_block") orelse return;    try extractTreeCallsFromNode(allocator, source, caller, body, stats, class_name);}fn extractTreeCallsFromNode(allocator: std.mem.Allocator, source: []const u8, caller: []const u8, node: tree.Node, stats: *Stats, class_name: ?[]const u8) !void {    const node_kind = tree.kind(node);    if (std.mem.eql(u8, node_kind, "new_expression")) return;    if (std.mem.eql(u8, node_kind, "call_expression")) {        const target = try treeCallTarget(allocator, source, node, class_name) orelse return;        if (!core.deferredEdgeExists(stats.*, caller, model.RelType.calls, target.name)) try stats.deferred.append(.{ .source = caller, .rel = model.RelType.calls, .target = target.name });        return;    }    const children = tree.childCount(node);    for (0..children) |raw_index| try extractTreeCallsFromNode(allocator, source, caller, tree.child(node, @intCast(raw_index)), stats, class_name);}fn treeCallTarget(allocator: std.mem.Allocator, source: []const u8, node: tree.Node, class_name: ?[]const u8) !?CallTarget {    const target_node = tree.childByField(node, "function") orelse core.namedChildByIndex(node, 0) orelse return null;    const target_kind = tree.kind(target_node);    if (std.mem.eql(u8, target_kind, "identifier")) {        const raw = tree.slice(source, target_node);        if (builtin(raw) or control(raw)) return null;        return .{ .name = try allocator.dupe(u8, raw), .resolved = false };    }    if (!std.mem.eql(u8, target_kind, "member_expression")) return null;    const object_node = tree.childByField(target_node, "object") orelse core.namedChildByIndex(target_node, 0) orelse return null;    const property_node = tree.childByField(target_node, "property") orelse core.namedChildByIndex(target_node, 1) orelse return null;    const object_kind = tree.kind(object_node);    const property_kind = tree.kind(property_node);    if ((!std.mem.eql(u8, object_kind, "identifier") and !std.mem.eql(u8, object_kind, "member_expression") and !std.mem.eql(u8, object_kind, "this") and !std.mem.eql(u8, object_kind, "super")) or (!std.mem.eql(u8, property_kind, "identifier") and !std.mem.eql(u8, property_kind, "property_identifier") and !std.mem.eql(u8, property_kind, "private_property_identifier"))) return null;    const object = tree.slice(source, object_node);    const property = tree.slice(source, property_node);    if (builtin(property) or control(property)) return null;    if (std.mem.eql(u8, object, "this") and class_name != null) return .{ .name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ class_name.?, property }), .resolved = true };    if (std.mem.eql(u8, object, "super") or builtin(object)) return null;    return .{ .name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ object, property }), .resolved = false };}fn functionBody(trimmed: []const u8) ?ScriptBody {    if (variableDeclaration(trimmed)) |decl| {        const value = text.trim(decl.value);        if (std.mem.indexOf(u8, value, "=>")) |arrow| {            const body = text.trim(value[arrow + "=>".len ..]);            if (std.mem.startsWith(u8, body, "{")) return .{ .fragment = body[1..], .braced = true };            return .{ .fragment = body, .braced = false };        }        if (std.mem.startsWith(u8, value, "function")) return bracedBody(value);    }    if (defaultFunctionValue(trimmed)) |value| {        if (std.mem.indexOf(u8, value, "=>")) |arrow| {            const body = text.trim(value[arrow + "=>".len ..]);            if (std.mem.startsWith(u8, body, "{")) return .{ .fragment = body[1..], .braced = true };            return .{ .fragment = body, .braced = false };        }        if (std.mem.startsWith(u8, value, "function")) return bracedBody(value);    }    return bracedBody(trimmed);}fn bracedBody(trimmed: []const u8) ?ScriptBody {    const open = std.mem.indexOfScalar(u8, trimmed, '{') orelse return null;    return .{ .fragment = trimmed[open + 1 ..], .braced = true };}fn extractCallsOutsideNestedArrows(allocator: std.mem.Allocator, caller: []const u8, fragment: []const u8, depth_before: i64, paren_depth_before: i64, stats: *Stats, class_name: ?[]const u8) !?ScriptArrowSkip {    if (std.mem.indexOf(u8, fragment, "=>")) |arrow| {        try extractCalls(allocator, caller, fragment[0..arrow], stats, class_name);        const body = text.trim(fragment[arrow + "=>".len ..]);        if (std.mem.startsWith(u8, body, "{")) return .{ .kind = .brace, .brace_depth = depth_before, .paren_depth = paren_depth_before };        if (body.len == 0 or parenDelta(fragment) > 0) return .{ .kind = .paren, .brace_depth = depth_before, .paren_depth = paren_depth_before };        return null;    }    try extractCalls(allocator, caller, fragment, stats, class_name);    return null;}fn extractCalls(allocator: std.mem.Allocator, caller: []const u8, fragment: []const u8, stats: *Stats, class_name: ?[]const u8) !void {    if (std.mem.startsWith(u8, text.trim(fragment), "//")) return;    var offset: usize = 0;    while (offset < fragment.len) {        const paren_rel = std.mem.indexOfScalar(u8, fragment[offset..], '(') orelse break;        const paren = offset + paren_rel;        offset = paren + 1;        const target = try callTarget(allocator, fragment, paren, class_name) orelse continue;        try stats.deferred.append(.{ .source = caller, .rel = model.RelType.calls, .target = target.name });    }}fn callTarget(allocator: std.mem.Allocator, fragment: []const u8, paren: usize, class_name: ?[]const u8) !?CallTarget {    if (paren == 0) return null;    var end = paren;    while (end > 0 and std.ascii.isWhitespace(fragment[end - 1])) end -= 1;    var start = end;    while (start > 0 and jsIdentByte(fragment[start - 1])) start -= 1;    if (start == end) return null;    var before = start;    while (before > 0 and std.ascii.isWhitespace(fragment[before - 1])) before -= 1;    const raw = fragment[start..end];    if (builtin(raw) or control(raw)) return null;    if (before > 0 and fragment[before - 1] == '.') {        const object = memberObject(fragment, before - 1) orelse return null;        if (precededByNew(fragment, object.start)) return null;        if (std.mem.eql(u8, object.value, "this") and class_name != null) return .{ .name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ class_name.?, raw }), .resolved = true };        if (std.mem.eql(u8, object.value, "super") or builtin(object.value)) return null;        return .{ .name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ object.value, raw }), .resolved = false };    }    if (precededByNew(fragment, start)) return null;    return .{ .name = try allocator.dupe(u8, raw), .resolved = false };}fn memberObject(fragment: []const u8, dot: usize) ?ScriptMemberObject {    if (dot == 0) return null;    var end = dot;    while (end > 0 and std.ascii.isWhitespace(fragment[end - 1])) end -= 1;    var start = end;    while (start > 0 and (jsIdentByte(fragment[start - 1]) or fragment[start - 1] == '.')) start -= 1;    if (start == end) return null;    return .{ .value = fragment[start..end], .start = start };}fn precededByNew(fragment: []const u8, callee_start: usize) bool {    var end = callee_start;    while (end > 0 and std.ascii.isWhitespace(fragment[end - 1])) end -= 1;    var start = end;    while (start > 0 and jsIdentByte(fragment[start - 1])) start -= 1;    return start != end and std.mem.eql(u8, fragment[start..end], "new");}fn control(name: []const u8) bool {    const controls = [_][]const u8{ "if", "for", "while", "switch", "catch", "function", "class", "return", "throw", "await", "new", "typeof", "delete", "void", "super", "of", "in" };    for (controls) |item| if (std.mem.eql(u8, name, item)) return true;    return false;}fn builtin(name: []const u8) bool {    const builtins = [_][]const u8{ "console", "require", "setTimeout", "Promise", "JSON", "Math", "Object", "Array", "String", "Number", "Boolean", "Date", "RegExp", "Error", "Map", "Set", "WeakMap", "WeakSet", "Symbol", "Proxy", "Reflect", "parseInt", "parseFloat", "isNaN", "isFinite", "encodeURI", "encodeURIComponent", "decodeURI", "decodeURIComponent", "fetch", "alert", "confirm", "prompt", "TypeError", "RangeError", "ReferenceError", "SyntaxError" };    for (builtins) |item| if (std.mem.eql(u8, name, item)) return true;    return false;}fn jsImport(trimmed: []const u8) ?[]const u8 {    if (std.mem.startsWith(u8, trimmed, "import ")) {        if (std.mem.indexOf(u8, trimmed, " from ")) |from| {            const rest = text.trim(trimmed[from + " from ".len ..]);            return core.quoted(rest);        }        return core.quoted(text.trim(trimmed["import ".len..]));    }    if (std.mem.startsWith(u8, trimmed, "require(")) return core.quoted(trimmed["require(".len..]);    return null;}fn importTarget(allocator: std.mem.Allocator, scratch: std.mem.Allocator, raw: []const u8, module: []const u8, path: []const u8) !?[]const u8 {    const stripped = scan_files.removeKnownExtension(raw);    if (std.mem.startsWith(u8, stripped, ".")) return try relativeImportTarget(allocator, scratch, stripped, module, path);    return try bareImportTarget(allocator, scratch, stripped);}fn bareImportTarget(allocator: std.mem.Allocator, scratch: std.mem.Allocator, raw: []const u8) !?[]const u8 {    var value = text.trim(raw);    if (value.len != 0 and value[0] == '@') value = value[1..];    var parts: std.ArrayList([]const u8) = .empty;    var split = std.mem.splitScalar(u8, value, '/');    while (split.next()) |part| {        const item = text.trim(part);        if (item.len == 0 or std.mem.eql(u8, item, ".")) continue;        try parts.append(scratch, item);    }    if (parts.items.len != 0 and std.mem.eql(u8, parts.items[parts.items.len - 1], "index")) _ = parts.pop();    if (parts.items.len == 0) return null;    return try core.joinDotted(allocator, parts.items);}fn relativeImportTarget(allocator: std.mem.Allocator, scratch: std.mem.Allocator, raw: []const u8, module: []const u8, path: []const u8) !?[]const u8 {    var resolved: std.ArrayList([]const u8) = .empty;    var module_split = std.mem.splitScalar(u8, module, '.');    while (module_split.next()) |part| try resolved.append(scratch, part);    const stem = scan_files.removeKnownExtension(text.basename(path));    if (!std.mem.eql(u8, stem, "index") and !std.mem.eql(u8, stem, "__init__") and resolved.items.len != 0) _ = resolved.pop();    var path_split = std.mem.splitScalar(u8, raw, '/');    while (path_split.next()) |part| {        const item = text.trim(part);        if (item.len == 0 or std.mem.eql(u8, item, ".")) continue;        if (std.mem.eql(u8, item, "..")) {            if (resolved.items.len == 0) return null;            _ = resolved.pop();            continue;        }        try resolved.append(scratch, item);    }    if (resolved.items.len != 0 and std.mem.eql(u8, resolved.items[resolved.items.len - 1], "index")) _ = resolved.pop();    if (resolved.items.len == 0) return null;    return try core.joinDotted(allocator, resolved.items);}fn className(trimmed: []const u8) ?[]const u8 {    if (prefixedName(trimmed, "class ")) |name| return name;    if (prefixedName(trimmed, "export class ")) |name| return name;    if (prefixedName(trimmed, "export default class ")) |name| return name;    if (prefixedName(trimmed, "abstract class ")) |name| return name;    if (prefixedName(trimmed, "export abstract class ")) |name| return name;    return null;}fn interfaceName(trimmed: []const u8) ?[]const u8 {    return prefixedName(trimmed, "interface ");}fn addHeritageEdges(allocator: std.mem.Allocator, stats: *Stats, class_name: []const u8, trimmed: []const u8) !void {    try addHeritageEdgesFor(allocator, stats, class_name, trimmed, "extends", model.RelType.inherits);    try addHeritageEdgesFor(allocator, stats, class_name, trimmed, "implements", model.RelType.implements);}fn addHeritageEdgesFor(allocator: std.mem.Allocator, stats: *Stats, class_name: []const u8, trimmed: []const u8, keyword: []const u8, rel: []const u8) !void {    const start = std.mem.indexOf(u8, trimmed, keyword) orelse return;    var rest = text.trim(trimmed[start + keyword.len ..]);    const brace = std.mem.indexOfScalar(u8, rest, '{') orelse rest.len;    rest = text.trim(rest[0..brace]);    var split = std.mem.splitScalar(u8, rest, ',');    while (split.next()) |raw| {        const target = heritageTarget(raw) orelse continue;        try stats.deferred.append(.{ .source = class_name, .rel = rel, .target = try allocator.dupe(u8, target) });    }}fn heritageTarget(raw: []const u8) ?[]const u8 {    const trimmed = text.trim(raw);    var end: usize = 0;    while (end < trimmed.len and (jsIdentByte(trimmed[end]) or trimmed[end] == '.')) end += 1;    if (end == 0) return null;    return trimmed[0..end];}fn functionName(trimmed: []const u8) ?[]const u8 {    if (prefixedName(trimmed, "function ")) |name| return name;    if (prefixedName(trimmed, "async function ")) |name| return name;    if (prefixedName(trimmed, "export function ")) |name| return name;    if (prefixedName(trimmed, "export async function ")) |name| return name;    if (prefixedName(trimmed, "export default function ")) |name| return name;    if (defaultFunctionValue(trimmed) != null) return "default";    const decl = variableDeclaration(trimmed) orelse return null;    const value = text.trim(decl.value);    if (std.mem.startsWith(u8, value, "function") or arrowValue(value)) return decl.name;    return null;}fn defaultFunctionValue(trimmed: []const u8) ?[]const u8 {    if (!std.mem.startsWith(u8, trimmed, "export default ")) return null;    const value = text.trim(trimmed["export default ".len..]);    if (std.mem.startsWith(u8, value, "function") or arrowValue(value)) return value;    return null;}fn prefixedName(trimmed: []const u8, prefix: []const u8) ?[]const u8 {    if (!std.mem.startsWith(u8, trimmed, prefix)) return null;    const rest = trimmed[prefix.len..];    var end: usize = 0;    while (end < rest.len and jsIdentByte(rest[end])) end += 1;    if (end == 0) return null;    return rest[0..end];}fn variableDeclaration(trimmed: []const u8) ?ScriptDecl {    var rest: []const u8 = undefined;    var value = trimmed;    if (std.mem.startsWith(u8, value, "export ")) value = text.trim(value["export ".len..]);    if (std.mem.startsWith(u8, value, "const ")) {        rest = value["const ".len..];    } else if (std.mem.startsWith(u8, value, "let ")) {        rest = value["let ".len..];    } else if (std.mem.startsWith(u8, value, "var ")) {        rest = value["var ".len..];    } else {        return null;    }    var end: usize = 0;    while (end < rest.len and jsIdentByte(rest[end])) end += 1;    if (end == 0) return null;    const after_name = text.trim(rest[end..]);    if (!std.mem.startsWith(u8, after_name, "=")) return null;    return .{ .name = rest[0..end], .value = after_name[1..] };}fn arrowValue(raw: []const u8) bool {    var value = raw;    if (std.mem.startsWith(u8, value, "async ")) value = text.trim(value["async ".len..]);    if (std.mem.startsWith(u8, value, "((")) return false;    const arrow = std.mem.indexOf(u8, value, "=>") orelse return false;    const call = std.mem.indexOfScalar(u8, value, '(');    if (call != null and call.? < arrow and !std.mem.startsWith(u8, value, "(")) return false;    return true;}fn constantName(trimmed: []const u8) ?[]const u8 {    const decl = variableDeclaration(trimmed) orelse return null;    return if (core.isUpper(decl.name)) decl.name else null;}fn methodName(trimmed: []const u8) ?[]const u8 {    if (std.mem.indexOf(u8, trimmed, "=>") != null) return null;    const paren = std.mem.indexOfScalar(u8, trimmed, '(') orelse return null;    var start = paren;    while (start > 0 and jsIdentByte(trimmed[start - 1])) start -= 1;    if (start == paren) return null;    const name = trimmed[start..paren];    if (control(name)) return null;    var prefix = text.trim(trimmed[0..start]);    if (prefix.len != 0) {        while (true) {            const before = prefix;            prefix = trimMethodModifier(prefix, "public");            prefix = trimMethodModifier(prefix, "private");            prefix = trimMethodModifier(prefix, "protected");            prefix = trimMethodModifier(prefix, "static");            prefix = trimMethodModifier(prefix, "async");            prefix = trimMethodModifier(prefix, "override");            prefix = trimMethodModifier(prefix, "get");            prefix = trimMethodModifier(prefix, "set");            if (std.mem.eql(u8, before, prefix)) break;        }        if (prefix.len != 0) return null;    }    return name;}fn trimMethodModifier(value: []const u8, modifier: []const u8) []const u8 {    if (std.mem.eql(u8, value, modifier)) return "";    if (std.mem.startsWith(u8, value, modifier) and value.len > modifier.len and std.ascii.isWhitespace(value[modifier.len])) return text.trim(value[modifier.len..]);    return value;}fn parenDelta(line: []const u8) i64 {    var delta: i64 = 0;    for (line) |byte| {        if (byte == '(') delta += 1;        if (byte == ')') delta -= 1;    }    return delta;}fn jsIdentByte(byte: u8) bool {    return identByte(byte) or byte == '$';}

Audit

Definitions4
Public names4
Members0
Version26.7.0
Revisiondaab053ee433