Functional Weave
Code in TypeScript

validation.uk-company-number@1.0.0

impl/typescript.ts

2,582 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { COMPANY_PREFIXES } from "./validation_uk_company_number_data.ts";  ← this capability’s own data, compiled from data/company-prefixes.json into the same file by fune build
import { type UkCompanyNumber } from "./validation_uk_company_number_types.ts";

/** Every company number is eight characters: eight digits, or a two-character prefix and six digits. */
const LENGTH = 8;

function invalid(reason: string): UkCompanyNumber {
  return { valid: false, normalised: null, prefix: null, jurisdiction: null, companyType: null, reason };
}

function isLetter(ch: string): boolean {
  return ch >= "A" && ch <= "Z";
}

function isDigit(ch: string): boolean {
  return ch >= "0" && ch <= "9";
}

/**
 * Check a Companies House company number's shape and prefix.
 *
 * There is no check digit, so a pass means the number is shaped like one
 * Companies House issues under a prefix it uses, not that the company exists:
 * that is a lookup on the register. Checks run in a fixed order (characters,
 * shape, length, prefix) so every language gives the same first reason.
 */
export function ukCompanyNumber(value: string): UkCompanyNumber {
  if (typeof value !== "string") return invalid("empty");
  let compact = "";
  for (let ch of value) {
    if (ch === " ") continue;
    // ASCII-only upper-casing, so every language folds identically.
    if (ch >= "a" && ch <= "z") ch = String.fromCharCode(ch.charCodeAt(0) - 32);
    if (!isLetter(ch) && !isDigit(ch)) return invalid("bad-character");
    compact += ch;
  }
  if (compact === "") return invalid("empty");

  // R0 (pre-partition Northern Ireland) is the one prefix containing a digit.
  let prefix: string;
  if (compact.startsWith("R0") || (compact.length >= 2 && isLetter(compact[0]) && isLetter(compact[1]))) {
    prefix = compact.slice(0, 2);
  } else if (isLetter(compact[0])) {
    return invalid("bad-format");
  } else {
    prefix = "";
  }
  const digits = compact.slice(prefix.length);
  for (const ch of digits) {
    if (!isDigit(ch)) return invalid("bad-format");
  }
  const width = LENGTH - prefix.length;
  if (digits.length === 0 || digits.length > width) return invalid("bad-length");
  // Leading zeros are part of the number and are often dropped when typed,
  // so they are put back; a number of nothing but zeros is no company.
  if (/^0+$/.test(digits)) return invalid("bad-format");

  const row = COMPANY_PREFIXES.find((r) => r.prefix === prefix);
  if (row === undefined) return invalid("unknown-prefix");
  return {
    valid: true,
    normalised: prefix + digits.padStart(width, "0"),
    prefix,
    jurisdiction: row.jurisdiction,
    companyType: row.companyType,
    reason: null,
  };
}