# shared/predicates/location.predicate.ts

> 181 lines of code and 38 definitions.

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

Listed in [shared/predicates](https://banes-lab.com/api/source/governance/shared/predicates.md), before [shared/predicates/text.predicate.ts](https://banes-lab.com/source/governance/shared/predicates/text.predicate.ts.md).

## Definitions

- `isProseContext` (lexical_declaration, line 70, exported)
- `holdsSsotCall` (lexical_declaration, line 169)
- `anchoredBySsot` (lexical_declaration, line 185, exported)
- `isPathShaped` (lexical_declaration, line 154, exported)
- `isDisqualified` (lexical_declaration, line 144)
- `isSsotExemptFile` (lexical_declaration, line 36, exported)
- `isSystemPath` (lexical_declaration, line 106)
- `resolvesOnDisk` (lexical_declaration, line 128)
- `exemptBasenames` (lexical_declaration, line 21)
- `SSOT_RULE_FILE` (lexical_declaration, line 17)
- `EXEMPT_SEGMENTS` (lexical_declaration, line 18)
- `SPECIFIER_PARENTS` (lexical_declaration, line 19)
- `files` (lexical_declaration, line 22)
- `EXEMPT_BASENAMES` (lexical_declaration, line 34)
- `posix` (lexical_declaration, line 37, exported)
- `isModuleSpecifier` (lexical_declaration, line 45, exported)
- `parent` (lexical_declaration, line 46, exported)
- `PROSE_KEYS` (lexical_declaration, line 50)
- `PROSE_CALLEES` (lexical_declaration, line 68)
- `NON_PATH_PREFIXES` (lexical_declaration, line 90)
- `MIN_PATH_SEGMENTS` (lexical_declaration, line 91)
- `MEDIA_TYPES` (lexical_declaration, line 93)
- `EXISTS_CACHE` (lexical_declaration, line 110)
- `probeBases` (lexical_declaration, line 112)
- `root` (lexical_declaration, line 113)
- `bases` (lexical_declaration, line 114)
- `cut` (lexical_declaration, line 117)
- `PROBE_BASES` (lexical_declaration, line 125)
- `PROBE_SUFFIXES` (lexical_declaration, line 126)
- `cached` (lexical_declaration, line 129)
- `found` (lexical_declaration, line 133)
- `literalHead` (lexical_declaration, line 138)
- `wild` (lexical_declaration, line 140)
- `head` (lexical_declaration, line 158, exported)
- `segments` (lexical_declaration, line 159, exported)
- `children` (lexical_declaration, line 176)
- `cursor` (lexical_declaration, line 186, exported)
- `composable` (lexical_declaration, line 188, exported)

## Uses

- [shared/matchers/filename.matcher.ts](https://banes-lab.com/source/governance/shared/matchers/filename.matcher.ts.md)
- [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)

## Source

```typescript
import {
    ANCHOR_SUBJECT,
    LOCATION_SUBJECT,
    WORKSPACE_ROOT,
    basenameOf,
    normalizePath,
    projectFiles,
} from "../resolvers/anchor.resolver.ts";
import { calleeName, nameOf, nodeAt, nodesAt } from "../selectors/syntax.selector.ts";
import type { AstNode } from "../../types/syntax.types.ts";
import { MEMBER_PATHS_CONFIGS } from "../resolvers/location.resolver.ts";
import { MEMBER_ROOTS } from "../registries/location.registry.ts";
import { existsSync } from "node:fs";
import { relativePath } from "@ssot/paths";
import { resolveFile } from "../matchers/filename.matcher.ts";

const SSOT_RULE_FILE = "closure-paths-via-ssot.eslint.rule.ts";
const EXEMPT_SEGMENTS = [`/${relativePath("project.paths")}/`, ".generated.", "/node_modules/"];
const SPECIFIER_PARENTS = new Set(["ImportDeclaration", "ExportNamedDeclaration", "ExportAllDeclaration"]);

const exemptBasenames = function exemptBasenames(): ReadonlySet<string> {
    const files = projectFiles();
    return new Set([
        SSOT_RULE_FILE,
        basenameOf(resolveFile(ANCHOR_SUBJECT, "resolver", files)),
        basenameOf(resolveFile(LOCATION_SUBJECT, "registry", files)),
        "taxonomy.config.ts",
        "paths-composition.test.ts",
        "paths-keys-resolve.test.ts",
        "location.registry.test.ts",
    ]);
};

const EXEMPT_BASENAMES = exemptBasenames();

export const isSsotExemptFile = function isSsotExemptFile(filename: string): boolean {
    const posix = normalizePath(filename);
    return (
        EXEMPT_SEGMENTS.some((segment) => posix.includes(segment)) ||
        EXEMPT_BASENAMES.has(basenameOf(posix)) ||
        MEMBER_PATHS_CONFIGS.has(posix)
    );
};

export const isModuleSpecifier = function isModuleSpecifier(node: AstNode): boolean {
    const parent = nodeAt(node, "parent");
    return parent !== null && SPECIFIER_PARENTS.has(parent.type);
};

const PROSE_KEYS = new Set([
    "description",
    "detail",
    "details",
    "docs",
    "hint",
    "label",
    "message",
    "messages",
    "note",
    "notes",
    "overview",
    "reason",
    "summary",
    "text",
    "title",
]);

const PROSE_CALLEES = new Set(["Error", "TypeError", "RangeError", "assert", "error", "warn", "log", "report"]);

export const isProseContext = function isProseContext(node: AstNode): boolean {
    let cursor = nodeAt(node, "parent");
    while (cursor !== null) {
        if (cursor.type === "Property" && PROSE_KEYS.has(nameOf(nodeAt(cursor, "key")))) {
            return true;
        }
        if (
            (cursor.type === "CallExpression" || cursor.type === "NewExpression") &&
            PROSE_CALLEES.has(calleeName(cursor))
        ) {
            return true;
        }
        if (cursor.type === "Program" || cursor.type === "FunctionDeclaration") {
            return false;
        }
        cursor = nodeAt(cursor, "parent");
    }
    return false;
};

const NON_PATH_PREFIXES = ["http://", "https://", "file://", "data:", "node:", "//"];
const MIN_PATH_SEGMENTS = 2;

const MEDIA_TYPES = new Set([
    "application",
    "audio",
    "example",
    "font",
    "image",
    "message",
    "model",
    "multipart",
    "text",
    "video",
]);

const isSystemPath = function isSystemPath(value: string): boolean {
    return value.startsWith("/");
};

const EXISTS_CACHE = new Map<string, boolean>();

const probeBases = function probeBases(): string[] {
    const root = normalizePath(WORKSPACE_ROOT);
    const bases = new Set([root]);
    for (const member of MEMBER_ROOTS) {
        bases.add(`${root}/${member}`);
        const cut = member.lastIndexOf("/");
        if (cut > 0) {
            bases.add(`${root}/${member.slice(0, cut)}`);
        }
    }
    return [...bases];
};

const PROBE_BASES = probeBases();
const PROBE_SUFFIXES = ["", ".ts"];

const resolvesOnDisk = function resolvesOnDisk(value: string): boolean {
    const cached = EXISTS_CACHE.get(value);
    if (cached !== undefined) {
        return cached;
    }
    const found = PROBE_BASES.some((base) => PROBE_SUFFIXES.some((suffix) => existsSync(`${base}/${value}${suffix}`)));
    EXISTS_CACHE.set(value, found);
    return found;
};

const literalHead = function literalHead(value: string): string {
    const segments = value.split("/");
    const wild = segments.findIndex((segment) => segment.includes("*") || segment.includes("{"));
    return wild === -1 ? value : segments.slice(0, wild).join("/");
};

const isDisqualified = function isDisqualified(value: string): boolean {
    if (value.length === 0 || value.includes(" ") || !value.includes("/")) {
        return true;
    }
    if (value.startsWith("@") || isSystemPath(value)) {
        return true;
    }
    return NON_PATH_PREFIXES.some((prefix) => value.startsWith(prefix));
};

export const isPathShaped = function isPathShaped(value: string): boolean {
    if (isDisqualified(value)) {
        return false;
    }
    const head = literalHead(value);
    const segments = head.split("/").filter((segment) => segment.length > 0);
    if (segments.length < MIN_PATH_SEGMENTS) {
        return false;
    }
    if (segments.length === MIN_PATH_SEGMENTS && MEDIA_TYPES.has(segments[0] ?? "")) {
        return false;
    }
    return resolvesOnDisk(head);
};

const holdsSsotCall = function holdsSsotCall(node: AstNode | null, ssotNames: ReadonlySet<string>): boolean {
    if (node === null) {
        return false;
    }
    if (node.type === "CallExpression" && ssotNames.has(calleeName(node))) {
        return true;
    }
    const children = [
        nodeAt(node, "left"),
        nodeAt(node, "right"),
        ...nodesAt(node, "expressions"),
        ...nodesAt(node, "arguments"),
    ];
    return children.some((child) => holdsSsotCall(child, ssotNames));
};

export const anchoredBySsot = function anchoredBySsot(node: AstNode, ssotNames: ReadonlySet<string>): boolean {
    let cursor = nodeAt(node, "parent");
    while (cursor !== null) {
        const composable = cursor.type === "TemplateLiteral" || cursor.type === "BinaryExpression";
        if (composable && holdsSsotCall(cursor, ssotNames)) {
            return true;
        }
        if (cursor.type === "CallExpression") {
            return nodesAt(cursor, "arguments").some((arg) => holdsSsotCall(arg, ssotNames));
        }
        if (cursor.type === "Program") {
            return false;
        }
        cursor = nodeAt(cursor, "parent");
    }
    return false;
};
```
