All files / utilities string.ts

100% Statements 31/31
100% Branches 8/8
100% Functions 5/5
100% Lines 29/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                      30x           30x   30x 19x     11x 14x   14x 11x                     9275x                 9275x                   5x   5x 1x     4x 4x 4x   4x                   14x 14x   14x 926x   926x 926x     14x 14x 14x 14x   14x   14x    
/**
 * Generates a unique URL-safe slug from a display name: lowercased, runs of
 * non-alphanumeric characters collapsed to `-`, trimmed, falling back to
 * {@link fallback} for names without usable characters. On a collision with an
 * existing id, a numeric suffix (`-2`, `-3`, ...) is appended.
 * @param name The display name to derive the slug from.
 * @param existingIds Ids the slug must not collide with.
 * @param fallback The slug used when the name yields no usable characters.
 * @returns A slug unique among the existing ids.
 */
export function generateUniqueSlug(name: string, existingIds: readonly string[], fallback: string): string {
	const slug = name
		.toLowerCase()
		.replace(/[^a-z0-9]+/g, '-')
		.replace(/^-+|-+$/g, '')
		|| fallback;
 
	const taken = new Set(existingIds);
 
	if (!taken.has(slug)) {
		return slug;
	}
 
	for (let suffix = 2; ; suffix++) {
		const candidate = `${slug}-${suffix}`;
 
		if (!taken.has(candidate)) {
			return candidate;
		}
	}
}
 
/**
 * Count the number of leading whitespace characters in a string.
 * @param str A string.
 * @returns The number of leading whitespace characters in the string.
 */
export function countLeadingWhitespace(str: string) {
	return str.length - str.trimStart().length;
}
 
/**
 * Count the number of trailing whitespace characters in a string.
 * @param str A string.
 * @returns The number of trailing whitespace characters in the string.
 */
export function countTrailingWhitespace(str: string) {
	return str.length - str.trimEnd().length;
}
 
/**
 * Find the first occurrence of a string in a text document and return its position.
 * @param document A text document.
 * @param text A string to search for in the document.
 * @returns The position of the string in the document or `undefined` if not found.
 */
export function findPosition(str: string, text: string): { line: number, character: number } | undefined {
	const index = str.indexOf(text);
 
	if (index === -1) {
		return undefined;
	}
 
	const beforeText = str.substring(0, index);
	const line = beforeText.split('\n').length - 1;
	const character = index - beforeText.lastIndexOf('\n') - 1;
 
	return { line, character };
}
/**
 * Computes a fingerprint of a text: its length and a 53-bit hash of its characters (cyrb53).
 * Used to recognize a text again without keeping it in memory; two different texts of the
 * same length share a fingerprint with negligible probability.
 * @param text The text.
 * @returns The fingerprint.
 */
export function getTextFingerprint(text: string): string {
	let hash1 = 0xdeadbeef;
	let hash2 = 0x41c6ce57;
 
	for (let i = 0; i < text.length; i++) {
		const code = text.charCodeAt(i);
 
		hash1 = Math.imul(hash1 ^ code, 2654435761);
		hash2 = Math.imul(hash2 ^ code, 1597334677);
	}
 
	hash1 = Math.imul(hash1 ^ (hash1 >>> 16), 2246822507);
	hash1 ^= Math.imul(hash2 ^ (hash2 >>> 13), 3266489909);
	hash2 = Math.imul(hash2 ^ (hash2 >>> 16), 2246822507);
	hash2 ^= Math.imul(hash1 ^ (hash1 >>> 13), 3266489909);
 
	const hash = 4294967296 * (2097151 & hash2) + (hash1 >>> 0);
 
	return `${text.length}:${hash.toString(36)}`;
}