core/selectors/markdown.selector.ts
core/selectors/markdown.selector.ts is a file in GovLab Docs. 73 lines of code and 26 definitions.
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;
};