# shared/analyzers/location.analyzer.ts

> 178 lines of code and 32 definitions.

Tree: Governance tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/governance#file-governance-shared-analyzers-location-analyzer-ts
Source text: https://banes-lab.com/source/governance/shared/analyzers/location.analyzer.ts.txt

Listed in [shared/analyzers](https://banes-lab.com/api/source/governance/shared/analyzers.md), after [shared/analyzers/liveness.analyzer.ts](https://banes-lab.com/source/governance/shared/analyzers/liveness.analyzer.ts.md) and before [shared/analyzers/rule.analyzer.ts](https://banes-lab.com/source/governance/shared/analyzers/rule.analyzer.ts.md).

## Definitions

- `holdsMeta` (lexical_declaration, line 80, exported)
- `isFileDir` (lexical_declaration, line 121, exported)
- `isMetaClimb` (lexical_declaration, line 174, exported)
- `boundNameOf` (lexical_declaration, line 165, exported)
- `cookedOf` (lexical_declaration, line 39, exported)
- `climbCountOf` (lexical_declaration, line 99)
- `staticTargetOf` (lexical_declaration, line 138, exported)
- `composedKeyOf` (lexical_declaration, line 55, exported)
- `PATH_CALLEES` (lexical_declaration, line 17)
- `CJS_ANCHORS` (lexical_declaration, line 36)
- `BUILDERS` (lexical_declaration, line 37)
- `trimSlashes` (lexical_declaration, line 43)
- `start` (lexical_declaration, line 44)
- `end` (lexical_declaration, line 45)
- `[anchor]` (lexical_declaration, line 60, exported)
- `keyValue` (lexical_declaration, line 64, exported)
- `tail` (lexical_declaration, line 68, exported)
- `anchorLocation` (lexical_declaration, line 72, exported)
- `key` (lexical_declaration, line 76, exported)
- `count` (lexical_declaration, line 100)
- `depthBelowPackage` (lexical_declaration, line 109, exported)
- `posix` (lexical_declaration, line 110, exported)
- `cut` (lexical_declaration, line 112, exported)
- `dir` (lexical_declaration, line 113, exported)
- `name` (lexical_declaration, line 126, exported)
- `segments` (lexical_declaration, line 146, exported)
- `sawFileDir` (lexical_declaration, line 147, exported)
- `parent` (lexical_declaration, line 166, exported)
- `id` (lexical_declaration, line 170, exported)
- `sawMeta` (lexical_declaration, line 178, exported)
- `climbs` (lexical_declaration, line 179, exported)
- `literal` (lexical_declaration, line 184, exported)

## Uses

- [shared/selectors/syntax.selector.ts](https://banes-lab.com/source/governance/shared/selectors/syntax.selector.ts.md)

## Used by

- [rules/eslint/closure-paths-via-ssot.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-paths-via-ssot.eslint.rule.ts.md)
- [shared/analyzers/segment.analyzer.ts](https://banes-lab.com/source/governance/shared/analyzers/segment.analyzer.ts.md)

## Source

```typescript
import {
    calleeName,
    isType,
    literalString,
    nameOf,
    nodeAt,
    nodesAt,
    recordAt,
    stringIn,
} from "../selectors/syntax.selector.ts";
import { keyForLocation, locationForKey } from "../registries/location.registry.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import type { ComposedKey } from "../../types/location.types.ts";
import { existsSync } from "node:fs";
import { normalizePath } from "../resolvers/anchor.resolver.ts";

const PATH_CALLEES = new Set([
    "join",
    "resolve",
    "relative",
    "readFileSync",
    "writeFileSync",
    "existsSync",
    "readdirSync",
    "statSync",
    "mkdirSync",
    "rmSync",
    "unlinkSync",
    "readFile",
    "writeFile",
    "readdir",
    "stat",
    "mkdir",
]);

const CJS_ANCHORS = new Set(["__dirname", "__filename"]);
const BUILDERS = new Set(["join", "resolve"]);

export const cookedOf = function cookedOf(node: AstNode | null): string {
    return stringIn(recordAt(node, "value"), "cooked");
};

const trimSlashes = function trimSlashes(value: string): string {
    let start = 0;
    let end = value.length;
    while (start < end && value[start] === "/") {
        start += 1;
    }
    while (end > start && value[end - 1] === "/") {
        end -= 1;
    }
    return value.slice(start, end);
};

export const composedKeyOf = function composedKeyOf(node: AstNode, ssotNames: ReadonlySet<string>): ComposedKey | null {
    const parent = nodeAt(node, "parent");
    if (!isType(parent, "TemplateLiteral")) {
        return null;
    }
    const [anchor] = nodesAt(parent, "expressions");
    if (anchor?.type !== "CallExpression" || !ssotNames.has(calleeName(anchor))) {
        return null;
    }
    const keyValue = literalString(nodesAt(anchor, "arguments")[0] ?? null);
    if (keyValue === null) {
        return null;
    }
    const tail = trimSlashes(cookedOf(node));
    if (tail.length === 0 || tail.includes("$")) {
        return null;
    }
    const anchorLocation = locationForKey(keyValue);
    if (anchorLocation === null) {
        return null;
    }
    const key = keyForLocation(`${anchorLocation}/${tail}`);
    return key === null ? null : { key, tail };
};

export const holdsMeta = function holdsMeta(node: AstNode | null): boolean {
    if (node === null) {
        return false;
    }
    if (node.type === "MetaProperty") {
        return true;
    }
    if (node.type === "Identifier" && CJS_ANCHORS.has(nameOf(node))) {
        return true;
    }
    if (node.type === "MemberExpression") {
        return holdsMeta(nodeAt(node, "object"));
    }
    if (node.type === "CallExpression") {
        return nodesAt(node, "arguments").some((arg) => holdsMeta(arg));
    }
    return false;
};

const climbCountOf = function climbCountOf(value: string): number {
    let count = 0;
    for (const segment of normalizePath(value).split("/")) {
        if (segment === "..") {
            count += 1;
        }
    }
    return count;
};

export const depthBelowPackage = function depthBelowPackage(filename: string): number {
    const posix = normalizePath(filename);
    const segments = posix.split("/");
    for (let cut = segments.length - 1; cut > 0; cut -= 1) {
        const dir = segments.slice(0, cut).join("/");
        if (existsSync(`${dir}/package.json`)) {
            return segments.length - 1 - cut;
        }
    }
    return 0;
};

export const isFileDir = function isFileDir(node: AstNode | null, dirNames: ReadonlySet<string>): boolean {
    if (node === null) {
        return false;
    }
    if (node.type === "Identifier") {
        const name = nameOf(node);
        return CJS_ANCHORS.has(name) || dirNames.has(name);
    }
    if (node.type === "MemberExpression") {
        return isType(nodeAt(node, "object"), "MetaProperty") && nameOf(nodeAt(node, "property")) === "dirname";
    }
    if (node.type === "CallExpression" && calleeName(node) === "dirname") {
        return nodesAt(node, "arguments").some((arg) => holdsMeta(arg));
    }
    return false;
};

export const staticTargetOf = function staticTargetOf(
    node: AstNode,
    dirNames: ReadonlySet<string>,
    fromDir: string,
): string | null {
    if (node.type !== "CallExpression" || !BUILDERS.has(calleeName(node))) {
        return null;
    }
    const segments: string[] = [];
    let sawFileDir = false;
    for (const arg of nodesAt(node, "arguments")) {
        if (isFileDir(arg, dirNames)) {
            sawFileDir = true;
            continue;
        }
        const literal = literalString(arg);
        if (literal === null) {
            return null;
        }
        segments.push(literal);
    }
    if (!sawFileDir || segments.length === 0) {
        return null;
    }
    return normalizePath([fromDir, ...segments].join("/"));
};

export const boundNameOf = function boundNameOf(node: AstNode): string | null {
    const parent = nodeAt(node, "parent");
    if (!isType(parent, "VariableDeclarator")) {
        return null;
    }
    const id = nodeAt(parent, "id");
    return isType(id, "Identifier") ? nameOf(id) : null;
};

export const isMetaClimb = function isMetaClimb(node: AstNode, metaNames: ReadonlySet<string>, depth: number): boolean {
    if (node.type !== "CallExpression" || !PATH_CALLEES.has(calleeName(node))) {
        return false;
    }
    let sawMeta = false;
    let climbs = 0;
    for (const arg of nodesAt(node, "arguments")) {
        if (holdsMeta(arg) || (arg.type === "Identifier" && metaNames.has(nameOf(arg)))) {
            sawMeta = true;
        }
        const literal = literalString(arg);
        if (literal !== null) {
            climbs += climbCountOf(literal);
        }
    }
    return sawMeta && climbs > depth;
};
```
