core/analyzers/code.go.analyzer.ts
core/analyzers/code.go.analyzer.ts is a file in GovLab Docs. 101 lines of code and 27 definitions.
import { GO_ENTRY, GO_STDLIB_NOISE } from "#configuration/constants/code.go.constants";
import type { GoCall, GoFunc, GoGraphContext, GoIndex } from "#types/code.types";
import { callsIn, parseFuncs } from "#core/parsers/code.go.parser";
import { GraphStore } from "#core/stores/graph.store";
import { lineAt } from "#core/lexers/code.go.lexer";
import { nodeId } from "#core/normalizers/diagram.normalizer";
import { readFileSync } from "node:fs";
import { sanitizeGo } from "#core/sanitizers/code.go.sanitizer";
const MIN_QUALIFIER_LEN = 2;
const funcId = function funcId(fn: GoFunc): string {
return nodeId(`go:${fn.file}:${fn.name}`);
};
const labelOf = function labelOf(fn: GoFunc): string {
return fn.receiver === null ? fn.name : `${fn.receiver}.${fn.name}`;
};
class GoGraphBuilder {
readonly #context: GoGraphContext;
public constructor(context: GoGraphContext) {
this.#context = context;
}
public seed(fn: GoFunc): void {
const id = funcId(fn);
const { store } = this.#context;
if (store.hasVisited(id)) {
return;
}
store.markVisited(id);
store.addNode({ id, kind: "entry", label: labelOf(fn), source: this.#sourceOf(fn) });
this.#walk(fn);
}
#sourceOf(fn: GoFunc): { file: string; line: number } {
return { file: this.#context.relPath(fn.file), line: fn.line };
}
#walk(fn: GoFunc): void {
const sanitized = this.#context.fileSrc.get(fn.file) ?? "";
for (const call of callsIn(sanitized, fn.bodyStart, fn.bodyEnd)) {
this.#handleCall(fn, call);
}
}
#handleCall(fn: GoFunc, call: GoCall): void {
if (call.qualifier !== null) {
this.#collaborator(fn, call, call.qualifier);
return;
}
const target = this.#context.funcMap.get(call.name);
if (target) {
this.#internalCall(fn, target);
}
}
#collaborator(fn: GoFunc, call: GoCall, qualifier: string): void {
if (GO_STDLIB_NOISE.has(qualifier) || qualifier.length < MIN_QUALIFIER_LEN) {
return;
}
const cid = nodeId(`go:collab:${qualifier}`);
this.#context.store.addNode({ id: cid, kind: "collaborator", label: qualifier, source: this.#sourceOf(fn) });
this.#context.store.pushEdge({ from: funcId(fn), kind: "call", label: call.name, to: cid });
}
#internalCall(fn: GoFunc, target: GoFunc): void {
const { store } = this.#context;
const tid = funcId(target);
store.pushEdge({ from: funcId(fn), kind: "call", to: tid });
if (store.hasVisited(tid)) {
store.pushEdge({ from: funcId(fn), kind: "loop", to: tid });
return;
}
store.markVisited(tid);
store.addNode({ id: tid, kind: "method", label: target.name, source: this.#sourceOf(target) });
this.#walk(target);
}
}
export const indexGoFiles = function indexGoFiles(files: readonly string[]): GoIndex {
const fileSrc = new Map<string, string>();
const funcMap = new Map<string, GoFunc>();
for (const file of files) {
const sanitized = sanitizeGo(readFileSync(file, "utf8"));
fileSrc.set(file, sanitized);
for (const fn of parseFuncs(sanitized)) {
if (!funcMap.has(fn.name)) {
funcMap.set(fn.name, { ...fn, file, line: lineAt(sanitized, fn.sigPos) });
}
}
}
return { fileSrc, funcMap };
};
const isUpperStart = function isUpperStart(name: string): boolean {
const first = name.charAt(0);
return first >= "A" && first <= "Z";
};
const seedFuncs = function seedFuncs(funcMap: ReadonlyMap<string, GoFunc>): GoFunc[] {
return [...funcMap.values()]
.filter((fn) => fn.name === GO_ENTRY || isUpperStart(fn.name))
.toSorted((left, right) => left.name.localeCompare(right.name));
};
export const buildGoGraph = function buildGoGraph(index: GoIndex, relPath: (file: string) => string): GraphStore {
const store = new GraphStore();
const builder = new GoGraphBuilder({ ...index, relPath, store });
for (const fn of seedFuncs(index.funcMap)) {
builder.seed(fn);
}
return store;
};