const FILE_LIKE_EXTENSION_PATTERN = /\.(?:md|markdown|txt|ya?ml|json|toml|csv|tsv|pdf|png|jpe?g|gif|svg|webp|avif|mp3|wav|ogg|mp4|mov|zip|tar|gz|tsx?|jsx?|cjs|mjs|rs|py|sh|css|html?)$/i const EXPLICIT_PROTOCOL_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i const MAYBE_PROTOCOL_PATTERN = /^[a-z][a-z0-9+.-]*:/i const LOCAL_PATH_PREFIX_PATTERN = /^(?:\.{1,2}\/|~\/|\/)/ const IPV4_HOST_PATTERN = /^\d{1,3}(\.\d{1,3}){3}$/ const WINDOWS_PATH_SEPARATOR = '\\' const WWW_PREFIX = 'www.' export type LinkValue = { raw: string } export type LinkMarkCandidate = { href: LinkValue text: LinkValue } function withRaw(raw: string): LinkValue { return { raw } } function stripUrlDecorators(value: LinkValue) { return withRaw(value.raw.split(/[?#]/, 1)[0] ?? value.raw) } function stripProtocol(value: LinkValue) { return withRaw(value.raw.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '')) } function isExplicitProtocolUrl(value: LinkValue) { return EXPLICIT_PROTOCOL_PATTERN.test(value.raw) } function isMaybeProtocolUrl(value: LinkValue) { return MAYBE_PROTOCOL_PATTERN.test(value.raw) } function hasWindowsPathSeparator(value: LinkValue) { return value.raw.includes(WINDOWS_PATH_SEPARATOR) } function hasPathSeparator(value: LinkValue) { return value.raw.includes('/') } function startsWithWebHostPrefix(value: LinkValue) { return value.raw.startsWith(WWW_PREFIX) } function firstPathSegment(value: LinkValue) { return value.raw.split('/', 1)[0] ?? value.raw } function isDomainLikePath(value: LinkValue) { return firstPathSegment(value).includes('.') } function hostnameFromUrlLikeValue(value: LinkValue) { const withoutUserinfo = value.raw.includes('@') ? value.raw.split('@').pop() ?? value.raw : value.raw return withoutUserinfo.split(/[/?#:]/, 1)[0] ?? withoutUserinfo } function isExplicitWebUrl(value: LinkValue) { return isExplicitProtocolUrl(value) || startsWithWebHostPrefix(value) } function isSingleSegmentValue(value: LinkValue) { return !hasPathSeparator(value) } function isLocalPathPrefix(value: LinkValue) { return LOCAL_PATH_PREFIX_PATTERN.test(value.raw) } function isPathLikeFileReference(value: LinkValue) { return hasPathSeparator(value) && !isDomainLikePath(value) } function hasBareProtocolPrefix(value: LinkValue) { return isMaybeProtocolUrl(value) && !value.raw.includes('@') } function hostnameHasTld(hostname: string) { return hostname.includes('.') } function normalizeInput(value: LinkValue) { return stripUrlDecorators(withRaw(value.raw.trim())) } export function looksLikeLocalFileReference(value: LinkValue) { const normalized = normalizeInput(value) if (!normalized.raw) return false if (isLocalPathPrefix(normalized) || hasWindowsPathSeparator(normalized)) { return true } if (!FILE_LIKE_EXTENSION_PATTERN.test(normalized.raw)) { return false } if (isExplicitWebUrl(normalized)) { return false } if (isSingleSegmentValue(normalized)) { return true } return isPathLikeFileReference(normalized) } export function shouldAutoLinkTolariaHref(url: LinkValue) { if (looksLikeLocalFileReference(url)) { return false } if (isExplicitProtocolUrl(url) || hasBareProtocolPrefix(url)) { return true } const hostname = hostnameFromUrlLikeValue(url) if (IPV4_HOST_PATTERN.test(hostname)) { return false } return hostnameHasTld(hostname) } export function shouldStripAutoLinkedLocalFileMark(mark: LinkMarkCandidate) { const normalizedText = normalizeInput(mark.text) if (!looksLikeLocalFileReference(normalizedText)) { return false } const normalizedHref = stripUrlDecorators( stripProtocol(normalizeInput(mark.href)), ) return normalizedHref.raw === normalizedText.raw }