Functional Weave
Code in Python

text.mask@1.0.0

impl/typescript.ts

1,521 bytes · the TypeScript implementation · view raw

/** Only these two are separators: they are how card and sort-code numbers are printed. */
function isSeparator(ch: string): boolean {
  return ch === " " || ch === "-";
}

/**
 * Mask all but the last `visible` characters of `value` with `maskChar`.
 *
 * Characters are Unicode code points, so an emoji or an accented letter
 * counts once and the result has as many code points as the input.
 */
export function mask(value: string, visible: number, maskChar: string, keepSeparators: boolean): string {
  if (typeof value !== "string") {
    throw new TypeError(`mask needs a string, received ${value}`);
  }
  if (!Number.isInteger(visible)) {
    throw new TypeError(`visible must be a whole number, received ${visible}`);
  }
  if (visible < 0) {
    throw new RangeError(`visible must be 0 or greater, received ${visible}`);
  }
  if (typeof maskChar !== "string" || Array.from(maskChar).length !== 1) {
    throw new RangeError(`maskChar must be exactly one character, received "${maskChar}"`);
  }

  // Iterating a string yields code points, not UTF-16 units.
  const chars = Array.from(value);
  let shown = 0;
  const out: string[] = new Array(chars.length);
  // Walk from the end so "the last n" is counted without a second pass.
  for (let i = chars.length - 1; i >= 0; i--) {
    const ch = chars[i];
    if (keepSeparators && isSeparator(ch)) {
      out[i] = ch;
    } else if (shown < visible) {
      out[i] = ch;
      shown++;
    } else {
      out[i] = maskChar;
    }
  }
  return out.join("");
}