# core/selectors/markdown.selector.ts

> 73 lines of code and 26 definitions.

Tree: GovLab Docs
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-selectors-markdown-selector-ts
Source text: https://banes-lab.com/source/docs/core/selectors/markdown.selector.ts.txt

Listed in [core/selectors](https://banes-lab.com/api/source/docs/core/selectors.md), after [core/selectors/code.typescript.selector.ts](https://banes-lab.com/source/docs/core/selectors/code.typescript.selector.ts.md) and before [core/selectors/metadata.selector.ts](https://banes-lab.com/source/docs/core/selectors/metadata.selector.ts.md).

## Definitions

- `isProsePath` (lexical_declaration, line 37, exported)
- `isTrailingEdge` (lexical_declaration, line 15)
- `extensionOf` (lexical_declaration, line 32)
- `pathSpans` (lexical_declaration, line 70, exported)
- `trimEdges` (lexical_declaration, line 20, exported)
- `pathSpanAt` (lexical_declaration, line 59)
- `PATH_EXTENSIONS` (lexical_declaration, line 3)
- `EDGE_PUNCT` (lexical_declaration, line 6)
- `INLINE_SPACE` (lexical_declaration, line 7)
- `RELATIVE_PREFIXES` (lexical_declaration, line 8)
- `SCHEME_MARKER` (lexical_declaration, line 9)
- `PACKAGE_MARK` (lexical_declaration, line 10)
- `SEPARATOR` (lexical_declaration, line 11)
- `DOT` (lexical_declaration, line 12)
- `DOUBLE_DOT` (lexical_declaration, line 13)
- `char` (lexical_declaration, line 16)
- `end` (lexical_declaration, line 22, exported)
- `dot` (lexical_declaration, line 33)
- `nextToken` (lexical_declaration, line 47, exported)
- `token` (lexical_declaration, line 60)
- `raw` (lexical_declaration, line 61)
- `trimmed` (lexical_declaration, line 62)
- `start` (lexical_declaration, line 66, exported)
- `spans` (lexical_declaration, line 71, exported)
- `at` (lexical_declaration, line 72, exported)
- `found` (lexical_declaration, line 74, exported)

## Contained in

- [core/selectors](https://banes-lab.com/anatomy/docs/folder-docs-core-selectors.md)

## Used by

- [core/analyzers/markdown.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/markdown.analyzer.ts.md)
- [core/converters/markdown.converter.ts](https://banes-lab.com/source/docs/core/converters/markdown.converter.ts.md)

## Linked from

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)
- [core/converters](https://banes-lab.com/anatomy/docs/folder-docs-core-converters.md)
- [core/selectors](https://banes-lab.com/anatomy/docs/folder-docs-core-selectors.md)

## Source

```typescript
import type { PathSpan, TokenSpan } from "#types/markdown.types";

const PATH_EXTENSIONS: ReadonlySet<string> = new Set(
    "ts tsx mts cts js jsx mjs cjs json jsonc md css scss go sh html yml yaml toml py rs java rb php sql".split(" "),
);
const EDGE_PUNCT: ReadonlySet<string> = new Set(["(", ")", "[", "]", ",", ";", ":", '"', "'", "!", "?", "`"]);
const INLINE_SPACE: ReadonlySet<string> = new Set([" ", "\t"]);
const RELATIVE_PREFIXES: readonly string[] = ["./", "../"];
const SCHEME_MARKER = "://";
const PACKAGE_MARK = "@";
const SEPARATOR = "/";
const DOT = ".";
const DOUBLE_DOT = "..";

const isTrailingEdge = function isTrailingEdge(token: string, end: number): boolean {
    const char = token.charAt(end - 1);
    return EDGE_PUNCT.has(char) || (char === DOT && token.charAt(end - DOUBLE_DOT.length) !== DOT);
};

export const trimEdges = function trimEdges(token: string): string {
    let start = 0;
    let end = token.length;
    while (start < end && EDGE_PUNCT.has(token.charAt(start))) {
        start += 1;
    }
    while (end > start && isTrailingEdge(token, end)) {
        end -= 1;
    }
    return token.slice(start, end);
};

const extensionOf = function extensionOf(token: string): string {
    const dot = token.lastIndexOf(DOT);
    return dot === -1 ? "" : token.slice(dot + 1).toLowerCase();
};

export const isProsePath = function isProsePath(token: string): boolean {
    if (!token.includes(SEPARATOR) || token.includes(SCHEME_MARKER) || token.startsWith(PACKAGE_MARK)) {
        return false;
    }
    if (RELATIVE_PREFIXES.some((prefix) => token.startsWith(prefix))) {
        return true;
    }
    return PATH_EXTENSIONS.has(extensionOf(token));
};

export const nextToken = function nextToken(prose: string, from: number): TokenSpan {
    let at = from;
    while (at < prose.length && INLINE_SPACE.has(prose.charAt(at))) {
        at += 1;
    }
    const start = at;
    while (at < prose.length && !INLINE_SPACE.has(prose.charAt(at))) {
        at += 1;
    }
    return { next: at, start };
};

const pathSpanAt = function pathSpanAt(prose: string, from: number): PathSpan {
    const token = nextToken(prose, from);
    const raw = prose.slice(token.start, token.next);
    const trimmed = trimEdges(raw);
    if (!isProsePath(trimmed)) {
        return { next: token.next, span: null };
    }
    const start = token.start + raw.indexOf(trimmed);
    return { next: token.next, span: [start, start + trimmed.length] };
};

export const pathSpans = function pathSpans(prose: string): [number, number][] {
    const spans: [number, number][] = [];
    let at = 0;
    while (at < prose.length) {
        const found = pathSpanAt(prose, at);
        if (found.span !== null) {
            spans.push(found.span);
        }
        at = found.next;
    }
    return spans;
};
```
