# core/matchers/leak.matcher.ts

> 85 lines of code and 20 definitions.

Tree: Build tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/build#file-build-core-matchers-leak-matcher-ts
Source text: https://banes-lab.com/assets/sources/source.7539c55c100d5458e7a70b227d6384c408d4e3748fd7e04913dcc141b08c6061.generated.txt

## Definitions

- `isWorkspacePath` (lexical_declaration, line 56)
- `identifierTokens` (lexical_declaration, line 52, exported)
- `plainTokens` (lexical_declaration, line 24)
- `hasIdentifierShape` (lexical_declaration, line 43)
- `leaksIn` (lexical_declaration, line 61, exported)
- `tokenReason` (lexical_declaration, line 72, exported)
- `spanReason` (lexical_declaration, line 66, exported)
- `IDENTIFIER_MARKS` (lexical_declaration, line 5)
- `inlineCodeSpans` (lexical_declaration, line 7)
- `open` (lexical_declaration, line 9)
- `close` (lexical_declaration, line 11)
- `span` (lexical_declaration, line 15)
- `tokens` (lexical_declaration, line 25)
- `current` (lexical_declaration, line 26)
- `cut` (lexical_declaration, line 57)
- `spans` (lexical_declaration, line 78, exported)
- `words` (lexical_declaration, line 82, exported)
- `reason` (lexical_declaration, line 83, exported)
- `seen` (lexical_declaration, line 86, exported)
- `fresh` (lexical_declaration, line 88, exported)

## Used by

- [core/loaders/leak.loader.ts](https://banes-lab.com/source/build/core/loaders/leak.loader.ts.md)
- [core/validators/leak.validator.ts](https://banes-lab.com/source/build/core/validators/leak.validator.ts.md)

## Source

```typescript
import { MIN_TOKEN_LENGTH, PATH_SEPARATOR, TOKEN_STOPS } from "#configuration/constants/leak.constants";
import { BACKTICK } from "#configuration/constants/inventory.constants";
import type { LeakMatch } from "#types/leak.types";

const IDENTIFIER_MARKS: ReadonlySet<string> = new Set(["_", "-", ".", "@", PATH_SEPARATOR]);

const inlineCodeSpans = function inlineCodeSpans(text: string): string[] {
    const spans: string[] = [];
    let open = text.indexOf(BACKTICK);
    while (open !== -1) {
        const close = text.indexOf(BACKTICK, open + 1);
        if (close === -1) {
            break;
        }
        const span = text.slice(open + 1, close).trim();
        if (span.length > 0) {
            spans.push(span);
        }
        open = text.indexOf(BACKTICK, close + 1);
    }
    return spans;
};

const plainTokens = function plainTokens(text: string): string[] {
    const tokens: string[] = [];
    let current = "";
    for (const char of text) {
        if (TOKEN_STOPS.has(char)) {
            if (current.length >= MIN_TOKEN_LENGTH) {
                tokens.push(current);
            }
            current = "";
        } else {
            current += char;
        }
    }
    if (current.length >= MIN_TOKEN_LENGTH) {
        tokens.push(current);
    }
    return tokens;
};

const hasIdentifierShape = function hasIdentifierShape(token: string): boolean {
    for (const char of token) {
        if (IDENTIFIER_MARKS.has(char)) {
            return true;
        }
    }
    return false;
};

export const identifierTokens = function identifierTokens(text: string): string[] {
    return plainTokens(text).filter(hasIdentifierShape);
};

const isWorkspacePath = function isWorkspacePath(token: string, roots: ReadonlySet<string>): boolean {
    const cut = token.indexOf(PATH_SEPARATOR);
    return cut > 0 && roots.has(token.slice(0, cut));
};

export const leaksIn = function leaksIn(
    text: string,
    tokens: ReadonlySet<string>,
    roots: ReadonlySet<string>,
): LeakMatch[] {
    const spanReason = function spanReason(span: string): LeakMatch["reason"] | null {
        if (tokens.has(span)) {
            return "inline-code";
        }
        return isWorkspacePath(span, roots) ? "path" : null;
    };
    const tokenReason = function tokenReason(token: string): LeakMatch["reason"] | null {
        if (isWorkspacePath(token, roots)) {
            return "path";
        }
        return hasIdentifierShape(token) && tokens.has(token) ? "identifier" : null;
    };
    const spans = inlineCodeSpans(text).flatMap((span) => {
        const reason = spanReason(span);
        return reason === null ? [] : [{ reason, token: span }];
    });
    const words = plainTokens(text).flatMap((token) => {
        const reason = tokenReason(token);
        return reason === null ? [] : [{ reason, token }];
    });
    const seen = new Set<string>();
    return [...spans, ...words].filter((match) => {
        const fresh = !seen.has(match.token);
        seen.add(match.token);
        return fresh;
    });
};
```
