All files / string to-csv.ts

100% Statements 17/17
100% Branches 24/24
100% Functions 5/5
100% Lines 16/16

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 441x 1x     22x 12x   10x 5x   5x 1x   4x       22x 22x 2x   22x                                 16x 22x   16x    
const NEEDS_QUOTES = /[\n\r"]|^\s|\s$/v;
const FORMULA_START = /^[\t\r+\-=@]/v;
 
function toText(value: unknown): string {
  if (typeof value === 'string') {
    return value;
  }
  if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
    return String(value);
  }
  if (value instanceof Date) {
    return value.toISOString();
  }
  return typeof value === 'object' && value !== null ? JSON.stringify(value) : '';
}
 
function formatField(value: unknown, separator: string, shouldEscapeFormulas: boolean): string {
  let text = toText(value);
  if (shouldEscapeFormulas && typeof value === 'string' && FORMULA_START.test(text)) {
    text = `'${text}`;
  }
  return text.includes(separator) || NEEDS_QUOTES.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
}
 
/**
 * Builds CSV text (RFC 4180): fields are quoted when needed, lines end with CRLF. Prepend `'\uFEFF'` so that
 * Excel reads UTF-8.
 *
 * @param rows - The rows, header first; `null` and `undefined` give empty fields, dates their ISO text,
 * objects their JSON.
 * @param separator - The field separator, such as `';'` for a locale with a decimal comma. Defaults to `','`.
 * @param shouldEscapeFormulas - Whether to prefix a text starting with `=`, `+`, `-` or `@` with `'`, so that a
 * spreadsheet does not run it. Defaults to `true`.
 * @returns The CSV text, without a final line break.
 * @example
 * toCsv([['time', 'speed'], [new Date(0), 12.5]]); // 'time,speed\r\n1970-01-01T00:00:00.000Z,12.5'
 */
export function toCsv(rows: Iterable<readonly unknown[]>, separator = ',', shouldEscapeFormulas = true): string {
  const lines: string[] = Array.from(rows, (row) =>
    row.map((value) => formatField(value, separator, shouldEscapeFormulas)).join(separator),
  );
  return lines.join('\r\n');
}