# tools/core/analyzers/graph.analyzer.ts

> 96 lines of code and 29 definitions.

Tree: Coordination tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-core-analyzers-graph-analyzer-ts
Source text: https://banes-lab.com/assets/sources/source.9616bee57147ac668583b8edd1dbafc457cbbcf0964fe9c672e8a8115f64dd74.generated.txt

## Definitions

- `localImports` (lexical_declaration, line 53, exported)
- `walk` (lexical_declaration, line 14, exported)
- `withoutTypeImports` (lexical_declaration, line 76)
- `moduleSource` (lexical_declaration, line 94, exported)
- `cyclesIn` (lexical_declaration, line 10, exported)
- `CYCLE_JOIN` (lexical_declaration, line 3)
- `IMPORT_LEAD` (lexical_declaration, line 4)
- `TYPE_IMPORT_LEAD` (lexical_declaration, line 5)
- `FROM_MARK` (lexical_declaration, line 6)
- `QUOTE` (lexical_declaration, line 7)
- `RELATIVE_LEADS` (lexical_declaration, line 8)
- `reported` (lexical_declaration, line 12, exported)
- `at` (lexical_declaration, line 15, exported)
- `cycle` (lexical_declaration, line 17, exported)
- `marker` (lexical_declaration, line 18, exported)
- `specifierIn` (lexical_declaration, line 39)
- `from` (lexical_declaration, line 40)
- `start` (lexical_declaration, line 44)
- `end` (lexical_declaration, line 45)
- `isRelative` (lexical_declaration, line 49)
- `directory` (lexical_declaration, line 55, exported)
- `inside` (lexical_declaration, line 56, exported)
- `specifier` (lexical_declaration, line 60, exported)
- `posix` (lexical_declaration, line 65, exported)
- `out` (lexical_declaration, line 77, exported)
- `skipping` (lexical_declaration, line 78)
- `trimmed` (lexical_declaration, line 81)
- `source` (lexical_declaration, line 99, exported)
- `combined` (lexical_declaration, line 100, exported)

## Used by

- [tools/core/validators/verdict.validator.ts](https://banes-lab.com/source/coordination/tools/core/validators/verdict.validator.ts.md)
- [tools/core/validators/writer.validator.ts](https://banes-lab.com/source/coordination/tools/core/validators/writer.validator.ts.md)

## Source

```typescript
import { dirname, resolve } from "node:path";

const CYCLE_JOIN = ">";
const IMPORT_LEAD = "import ";
const TYPE_IMPORT_LEAD = "import type ";
const FROM_MARK = 'from "';
const QUOTE = '"';
const RELATIVE_LEADS = ["./", "../"];

export const cyclesIn = function cyclesIn(edges: ReadonlyMap<string, ReadonlySet<string>>): string[][] {
    const out: string[][] = [];
    const reported = new Set<string>();

    const walk = (node: string, trail: readonly string[]): void => {
        const at = trail.indexOf(node);
        if (at !== -1) {
            const cycle = trail.slice(at);
            const marker = cycle.toSorted((left, right) => left.localeCompare(right)).join(CYCLE_JOIN);
            if (!reported.has(marker)) {
                reported.add(marker);
                out.push([...cycle]);
            }
            return;
        }
        if (trail.length > edges.size) {
            return;
        }
        for (const next of edges.get(node) ?? []) {
            walk(next, [...trail, node]);
        }
    };

    for (const node of edges.keys()) {
        walk(node, []);
    }
    return out;
};

const specifierIn = function specifierIn(line: string): string | null {
    const from = line.indexOf(FROM_MARK);
    if (from === -1) {
        return null;
    }
    const start = from + FROM_MARK.length;
    const end = line.indexOf(QUOTE, start);
    return end === -1 ? "" : line.slice(start, end);
};

const isRelative = function isRelative(specifier: string): boolean {
    return RELATIVE_LEADS.some((lead) => specifier.startsWith(lead));
};

export const localImports = function localImports(path: string, source: string, known: ReadonlySet<string>): string[] {
    const out = new Set<string>();
    const directory = dirname(path);
    let inside = false;

    for (const line of source.split("\n")) {
        inside ||= line.trim().startsWith(IMPORT_LEAD);
        const specifier = inside ? specifierIn(line) : null;
        if (specifier === null) {
            continue;
        }
        inside = false;
        const posix = isRelative(specifier) ? resolve(directory, specifier).replaceAll("\\", "/") : "";
        for (const candidate of known) {
            if (posix.length > 0 && posix.endsWith(candidate)) {
                out.add(candidate);
            }
        }
    }

    return [...out];
};

const withoutTypeImports = function withoutTypeImports(source: string): string {
    const out: string[] = [];
    let skipping = false;

    for (const line of source.split("\n")) {
        const trimmed = line.trim();
        skipping ||= trimmed.startsWith(TYPE_IMPORT_LEAD);
        if (!skipping) {
            out.push(line);
        }
        if (skipping && trimmed.includes(FROM_MARK)) {
            skipping = false;
        }
    }

    return out.join("\n");
};

export const moduleSource = function moduleSource(
    path: string,
    read: (path: string) => string,
    known: ReadonlySet<string>,
): string {
    const source = read(path);
    let combined = source;

    for (const imported of localImports(path, withoutTypeImports(source), known)) {
        try {
            combined += `\n${read(imported)}`;
        } catch {
            continue;
        }
    }

    return combined;
};
```
