2,517 bytes · the TypeScript implementation · view raw
/** * The Unicode White_Space property, spelled out so all three languages agree: * JavaScript's \s, Python's str.isspace and Rust's char::is_whitespace each * draw the line slightly differently. */function isWhitespace(cp: number): boolean {
return (
(cp >= 0x09 && cp <= 0x0d) ||
cp === 0x20 ||
cp === 0x85 ||
cp === 0xa0 ||
cp === 0x1680 ||
(cp >= 0x2000 && cp <= 0x200a) ||
cp === 0x2028 ||
cp === 0x2029 ||
cp === 0x202f ||
cp === 0x205f ||
cp === 0x3000
);
}
/** Punctuation that reads badly directly before an ellipsis. */function isTrailingPunctuation(ch: string): boolean {
return ch === "," || ch === ";" || ch === ":" || ch === "-" || ch === ".";
}
function codePoint(ch: string): number {
return ch.codePointAt(0) as number;
}
/** * Shorten `value` to at most `maxLength` code points, ellipsis included, * cutting at a word boundary where there is one. */exportfunction truncate(value: string, maxLength: number, ellipsis: string): string {
if (typeof value !== "string") {
thrownew TypeError(`truncate needs a string, received ${value}`);
}
if (!Number.isInteger(maxLength)) {
thrownew TypeError(`maxLength must be a whole number, received ${maxLength}`);
}
if (maxLength < 0) {
thrownew RangeError(`maxLength must be 0 or greater, received ${maxLength}`);
}
if (typeof ellipsis !== "string") {
thrownew TypeError(`ellipsis must be a string, received ${ellipsis}`);
}
// Code points, not UTF-16 units: an emoji is one character here, as it is// in Python and Rust.const chars = Array.from(value);
const mark = Array.from(ellipsis);
if (mark.length > maxLength) {
thrownew RangeError(`maxLength must be at least the length of the ellipsis, received ${maxLength}`);
}
if (chars.length <= maxLength) return value;
const budget = maxLength - mark.length;
let end = budget;
// A cut just before whitespace is already a clean boundary.if (!isWhitespace(codePoint(chars[budget]))) {
let lastSpace = -1;
for (let i = budget - 1; i >= 0; i--) {
if (isWhitespace(codePoint(chars[i]))) {
lastSpace = i;
break;
}
}
// No boundary to move back to: one long word is cut hard rather than// reduced to a bare ellipsis.if (lastSpace > 0) end = lastSpace;
}
while (end > 0 && (isWhitespace(codePoint(chars[end - 1])) || isTrailingPunctuation(chars[end - 1]))) {
end--;
}
return chars.slice(0, end).join("") + ellipsis;
}