From f4e7a9025891a10368f9eabf41b809f50ba7b7cf Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Tue, 31 Mar 2026 20:17:51 -0300 Subject: [PATCH 01/10] feat: add Brazilian passport validation utilities --- src/passport/passport.ts | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/passport/passport.ts diff --git a/src/passport/passport.ts b/src/passport/passport.ts new file mode 100644 index 00000000..0a15348f --- /dev/null +++ b/src/passport/passport.ts @@ -0,0 +1,72 @@ +/** + * Checks if a Brazilian passport number is valid. + * To be considered valid, the input must be a string containing exactly two + * alphabetical characters followed by exactly six numerical digits. + * This function does not verify if the input is a real passport number, + * as there are no checksums for the Brazilian passport. + * + * @param passport - The string containing the passport number to be checked. + * @returns True if the passport number is valid (2 letters followed by 6 digits). + * + * @example + * isValidPassport("Ab123456") // false - must be uppercase + * isValidPassport("AB123456") // true + * isValidPassport("12345678") // false + * isValidPassport("DC-221345") // false + */ +export const isValidPassport = (passport: string): boolean => { + if (typeof passport !== "string") return false; + return /^[A-Z]{2}[0-9]{6}$/.test(passport); +}; + +/** + * Removes symbols ('-', '.', and whitespaces) from a passport number. + * + * @param passport - The string containing a passport number. + * @returns The passport number with dashes, dots, and whitespaces removed. + * + * @example + * removeSymbolsFromPassport("Ab123456") // "Ab123456" + * removeSymbolsFromPassport("Ab-123456") // "Ab123456" + * removeSymbolsFromPassport("Ab -. 123456") // "Ab123456" + */ +export const removeSymbolsFromPassport = (passport: string): string => + passport.replace(/[\s.\-]/g, ""); + +/** + * Formats a Brazilian passport number for display. + * Returns the passport uppercased and without symbols, or null if invalid. + * + * @param passport - A Brazilian passport number (any case, possibly with symbols). + * @returns The formatted passport number (uppercase, no symbols), or null if invalid. + * + * @example + * formatPassport("Ab123456") // "AB123456" + * formatPassport("Ab-123456") // "AB123456" + * formatPassport("111111") // null + */ +export const formatPassport = (passport: string): string | null => { + const cleaned = removeSymbolsFromPassport(passport.toUpperCase()); + return isValidPassport(cleaned) ? cleaned : null; +} + +/** + * Generates a random valid Brazilian passport number. + * + * @returns A random valid passport number string (e.g. "RY393097"). + * + * @example + * generatePassport() // "RY393097" + * generatePassport() // "ZS840088" + */ +export const generatePassport = (): string => { + const letters = Array.from({ length: 2 }, () => + String.fromCharCode(65 + Math.floor(Math.random() * 26)) + ).join(""); + + const digits = Array.from({ length: 6 }, () => + Math.floor(Math.random() * 10) + ).join(""); + + return `${letters}${digits}`; +} From b4df189720ba9285cbabaeb1a69ae0fe691521b3 Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Tue, 31 Mar 2026 20:18:27 -0300 Subject: [PATCH 02/10] test: add unit tests for passport utilities --- src/index.test.ts | 4 ++ src/passport/passport.test.ts | 78 +++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/passport/passport.test.ts diff --git a/src/index.test.ts b/src/index.test.ts index b9d45cc6..2dbe6dcf 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -27,6 +27,10 @@ const PUBLIC = [ "parseCpf", "parseCurrency", "parsePhone", + "isValidPassport", + "generatePassport", + "removeSymbolsFromPassport", + "formatPassport", "formatPis", "parsePis", "parseProcessoJuridico", diff --git a/src/passport/passport.test.ts b/src/passport/passport.test.ts new file mode 100644 index 00000000..b5d18c51 --- /dev/null +++ b/src/passport/passport.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, test, vi, beforeEach } from "vitest"; +import { + isValidPassport, + generatePassport, + removeSymbolsFromPassport, + formatPassport, +} from "./passport"; + +describe("passport", () => { + describe("isValidPassport", () => { + describe("should return false", () => { + test("when passport is not a string", () => { + expect(isValidPassport(1 as any)).toBe(false); + }); + + test("when passport length is different from 8", () => { + expect(isValidPassport("1")).toBe(false); + }); + + test("when passport does not match the expected format", () => { + expect(isValidPassport("1112223334-")).toBe(false); + }); + }); + + describe("should return true", () => { + test("when passport is valid", () => { + expect(isValidPassport("AA111111")).toBe(true); + expect(isValidPassport("CL125167")).toBe(true); + }); + }); + }); + + describe("generatePassport", () => { + test("should always generate a valid passport", () => { + for (let i = 0; i < 10_000; i++) { + expect(isValidPassport(generatePassport())).toBe(true); + } + }); + }); + + describe("removeSymbolsFromPassport", () => { + test("when there are no symbols, returns the same string", () => { + expect(removeSymbolsFromPassport("Ab123456")).toBe("Ab123456"); + }); + + test("when there are spaces, returns the string without them", () => { + expect(removeSymbolsFromPassport(" AB 123 456 ")).toBe("AB123456"); + }); + + test("when there are dashes, returns the string without them", () => { + expect(removeSymbolsFromPassport("-AB1-23-4-56-")).toBe("AB123456"); + }); + + test("when there are dots, returns the string without them", () => { + expect(removeSymbolsFromPassport(".AB.1.23.456.")).toBe("AB123456"); + }); + + test("when there are multiple symbols, returns the string without any of them", () => { + expect(removeSymbolsFromPassport(".A B.1.2-3.45 -. 6.")).toBe("AB123456"); + }); + }); + + describe("formatPassport", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + test("when passport is valid, returns the formatted passport", () => { + vi.spyOn({ isValidPassport }, "isValidPassport").mockReturnValue(true); + expect(formatPassport("yz 987654")).toBe("YZ987654"); + }); + + test("when passport is not valid, returns null", () => { + vi.spyOn({ isValidPassport }, "isValidPassport").mockReturnValue(false); + expect(formatPassport("acd12736")).toBeNull(); + }); + }); +}); From 1e3deaa846fb7a921754da9ca1b6f59bfad789a5 Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Tue, 31 Mar 2026 20:57:11 -0300 Subject: [PATCH 03/10] fix: replace as any with as unknown as string in test --- src/passport/passport.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passport/passport.test.ts b/src/passport/passport.test.ts index b5d18c51..9401cc56 100644 --- a/src/passport/passport.test.ts +++ b/src/passport/passport.test.ts @@ -10,7 +10,7 @@ describe("passport", () => { describe("isValidPassport", () => { describe("should return false", () => { test("when passport is not a string", () => { - expect(isValidPassport(1 as any)).toBe(false); + expect(isValidPassport(1 as unknown as string)).toBe(false); }); test("when passport length is different from 8", () => { From 5f90a74872eb7f61ed9992b6f3c3545e33abb2e3 Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Wed, 1 Apr 2026 19:27:09 -0300 Subject: [PATCH 04/10] refactor: split passport functions into separate modules --- src/format-passport/format-passport.test.ts | 23 ++++++ src/format-passport/format-passport.ts | 19 +++++ .../generate-passport.test.ts | 11 +++ src/generate-passport/generate-passport.ts | 20 +++++ src/index.test.ts | 6 +- src/index.ts | 4 + .../is-valid-passport.test.ts | 25 ++++++ src/is-valid-passport/is-valid-passport.ts | 20 +++++ src/parse-passport/parse-passport.test.ts | 26 +++++++ src/parse-passport/parse-passport.ts | 13 ++++ src/passport/passport.test.ts | 78 ------------------- src/passport/passport.ts | 72 ----------------- 12 files changed, 164 insertions(+), 153 deletions(-) create mode 100644 src/format-passport/format-passport.test.ts create mode 100644 src/format-passport/format-passport.ts create mode 100644 src/generate-passport/generate-passport.test.ts create mode 100644 src/generate-passport/generate-passport.ts create mode 100644 src/is-valid-passport/is-valid-passport.test.ts create mode 100644 src/is-valid-passport/is-valid-passport.ts create mode 100644 src/parse-passport/parse-passport.test.ts create mode 100644 src/parse-passport/parse-passport.ts delete mode 100644 src/passport/passport.test.ts delete mode 100644 src/passport/passport.ts diff --git a/src/format-passport/format-passport.test.ts b/src/format-passport/format-passport.test.ts new file mode 100644 index 00000000..a4cafef6 --- /dev/null +++ b/src/format-passport/format-passport.test.ts @@ -0,0 +1,23 @@ +import { beforeEach, describe, expect, test, vi } from "vitest"; +import { isValidPassport } from "../is-valid-passport/is-valid-passport"; +import { formatPassport } from "./format-passport"; + +describe("formatPassport", () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + describe("should return the formatted passport", () => { + test("when passport is valid", () => { + vi.spyOn({ isValidPassport }, "isValidPassport").mockReturnValue(true); + expect(formatPassport("yz 987654")).toBe("YZ987654"); + }); + }); + + describe("should return null", () => { + test("when passport is not valid", () => { + vi.spyOn({ isValidPassport }, "isValidPassport").mockReturnValue(false); + expect(formatPassport("acd12736")).toBeNull(); + }); + }); +}); diff --git a/src/format-passport/format-passport.ts b/src/format-passport/format-passport.ts new file mode 100644 index 00000000..10c3ac27 --- /dev/null +++ b/src/format-passport/format-passport.ts @@ -0,0 +1,19 @@ +import { isValidPassport } from "../is-valid-passport/is-valid-passport"; +import { parsePassport } from "../parse-passport/parse-passport"; + +/** + * Formats a Brazilian passport number for display. + * Returns the passport uppercased and without symbols, or null if invalid. + * + * @param passport - A Brazilian passport number (any case, possibly with symbols). + * @returns The formatted passport number (uppercase, no symbols), or null if invalid. + * + * @example + * formatPassport("Ab123456") // "AB123456" + * formatPassport("Ab-123456") // "AB123456" + * formatPassport("111111") // null + */ +export const formatPassport = (passport: string): string | null => { + const cleaned = parsePassport(passport.toUpperCase()); + return isValidPassport(cleaned) ? cleaned : null; +}; diff --git a/src/generate-passport/generate-passport.test.ts b/src/generate-passport/generate-passport.test.ts new file mode 100644 index 00000000..b5eb85d6 --- /dev/null +++ b/src/generate-passport/generate-passport.test.ts @@ -0,0 +1,11 @@ +import { describe, expect, test } from "vitest"; +import { isValidPassport } from "../is-valid-passport/is-valid-passport"; +import { generatePassport } from "./generate-passport"; + +describe("generatePassport", () => { + test("should always generate a valid passport", () => { + for (let i = 0; i < 10_000; i++) { + expect(isValidPassport(generatePassport())).toBe(true); + } + }); +}); diff --git a/src/generate-passport/generate-passport.ts b/src/generate-passport/generate-passport.ts new file mode 100644 index 00000000..e2306b89 --- /dev/null +++ b/src/generate-passport/generate-passport.ts @@ -0,0 +1,20 @@ +/** + * Generates a random valid Brazilian passport number. + * + * @returns A random valid passport number string (e.g. "RY393097"). + * + * @example + * generatePassport() // "RY393097" + * generatePassport() // "ZS840088" + */ +export const generatePassport = (): string => { + const letters = Array.from({ length: 2 }, () => + String.fromCharCode(65 + Math.floor(Math.random() * 26)), + ).join(""); + + const digits = Array.from({ length: 6 }, () => + Math.floor(Math.random() * 10), + ).join(""); + + return `${letters}${digits}`; +}; diff --git a/src/index.test.ts b/src/index.test.ts index 2dbe6dcf..605cc73c 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -28,9 +28,9 @@ const PUBLIC = [ "parseCurrency", "parsePhone", "isValidPassport", - "generatePassport", - "removeSymbolsFromPassport", - "formatPassport", + "generatePassport", + "parsePassport", + "formatPassport", "formatPis", "parsePis", "parseProcessoJuridico", diff --git a/src/index.ts b/src/index.ts index 9eb8b364..c91d2138 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ export { type FormatCepOptions, formatCep } from "./format-cep/format-cep"; export { type FormatCnpjOptions, formatCnpj } from "./format-cnpj/format-cnpj"; export { type FormatCpfOptions, formatCpf } from "./format-cpf/format-cpf"; export { type FormatCurrencyOptions, formatCurrency } from "./format-currency/format-currency"; +export { formatPassport } from "./format-passport/format-passport"; export { type FormatPhoneOptions, formatPhone } from "./format-phone/format-phone"; export { type FormatPisOptions, formatPis } from "./format-pis/format-pis"; export { @@ -13,6 +14,7 @@ export { export { generateBoleto } from "./generate-boleto/generate-boleto"; export { generateCnpj } from "./generate-cnpj/generate-cnpj"; export { generateCpf } from "./generate-cpf/generate-cpf"; +export { generatePassport } from "./generate-passport/generate-passport"; export { GetAddressInfoByCepError, GetAddressInfoByCepNotFoundError, @@ -41,6 +43,7 @@ export { type IsValidMobilePhoneOptions, isValidMobilePhone, } from "./is-valid-mobile-phone/is-valid-mobile-phone"; +export { isValidPassport } from "./is-valid-passport/is-valid-passport"; export { type IsValidPhoneOptions, isValidPhone } from "./is-valid-phone/is-valid-phone"; export { isValidPis } from "./is-valid-pis/is-valid-pis"; export { isValidProcessoJuridico } from "./is-valid-processo-juridico/is-valid-processo-juridico"; @@ -53,6 +56,7 @@ export { parseCurrency } from "./parse-currency/parse-currency"; export { parsePhone } from "./parse-phone/parse-phone"; export { parsePis } from "./parse-pis/parse-pis"; export { parseProcessoJuridico } from "./parse-processo-juridico/parse-processo-juridico"; +export { parsePassport } from "./parse-passport/parse-passport"; // ============================================================================ // DEPRECATED EXPORTS - Will be removed in v3.0.0 diff --git a/src/is-valid-passport/is-valid-passport.test.ts b/src/is-valid-passport/is-valid-passport.test.ts new file mode 100644 index 00000000..439954e1 --- /dev/null +++ b/src/is-valid-passport/is-valid-passport.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, test } from "vitest"; +import { isValidPassport } from "./is-valid-passport"; + +describe("isValidPassport", () => { + describe("should return false", () => { + test("when passport is not a string", () => { + expect(isValidPassport(1 as unknown as string)).toBe(false); + }); + + test("when passport length is different from 8", () => { + expect(isValidPassport("1")).toBe(false); + }); + + test("when passport does not match the expected format", () => { + expect(isValidPassport("1112223334-")).toBe(false); + }); + }); + + describe("should return true", () => { + test("when passport is valid", () => { + expect(isValidPassport("AA111111")).toBe(true); + expect(isValidPassport("CL125167")).toBe(true); + }); + }); +}); diff --git a/src/is-valid-passport/is-valid-passport.ts b/src/is-valid-passport/is-valid-passport.ts new file mode 100644 index 00000000..60b8ff6a --- /dev/null +++ b/src/is-valid-passport/is-valid-passport.ts @@ -0,0 +1,20 @@ +/** + * Checks if a Brazilian passport number is valid. + * To be considered valid, the input must be a string containing exactly two + * alphabetical characters followed by exactly six numerical digits. + * This function does not verify if the input is a real passport number, + * as there are no checksums for the Brazilian passport. + * + * @param passport - The string containing the passport number to be checked. + * @returns True if the passport number is valid (2 letters followed by 6 digits). + * + * @example + * isValidPassport("Ab123456") // false - must be uppercase + * isValidPassport("AB123456") // true + * isValidPassport("12345678") // false + * isValidPassport("DC-221345") // false + */ +export const isValidPassport = (passport: string): boolean => { + if (typeof passport !== "string") return false; + return /^[A-Z]{2}[0-9]{6}$/.test(passport); +}; diff --git a/src/parse-passport/parse-passport.test.ts b/src/parse-passport/parse-passport.test.ts new file mode 100644 index 00000000..ef6800b1 --- /dev/null +++ b/src/parse-passport/parse-passport.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, test } from "vitest"; +import { parsePassport } from "./parse-passport"; + +describe("parsePassport", () => { + describe("should return the string without symbols", () => { + test("when there are no symbols, returns the same string", () => { + expect(parsePassport("Ab123456")).toBe("Ab123456"); + }); + + test("when there are spaces", () => { + expect(parsePassport(" AB 123 456 ")).toBe("AB123456"); + }); + + test("when there are dashes", () => { + expect(parsePassport("-AB1-23-4-56-")).toBe("AB123456"); + }); + + test("when there are dots", () => { + expect(parsePassport(".AB.1.23.456.")).toBe("AB123456"); + }); + + test("when there are multiple symbols", () => { + expect(parsePassport(".A B.1.2-3.45 -. 6.")).toBe("AB123456"); + }); + }); +}); diff --git a/src/parse-passport/parse-passport.ts b/src/parse-passport/parse-passport.ts new file mode 100644 index 00000000..a382f931 --- /dev/null +++ b/src/parse-passport/parse-passport.ts @@ -0,0 +1,13 @@ +/** + * Removes symbols ('-', '.', and whitespaces) from a passport number. + * + * @param passport - The string containing a passport number. + * @returns The passport number with dashes, dots, and whitespaces removed. + * + * @example + * parsePassport("Ab123456") // "Ab123456" + * parsePassport("Ab-123456") // "Ab123456" + * parsePassport("Ab -. 123456") // "Ab123456" + */ +export const parsePassport = (passport: string): string => + passport.replace(/[\s.-]/g, ""); diff --git a/src/passport/passport.test.ts b/src/passport/passport.test.ts deleted file mode 100644 index 9401cc56..00000000 --- a/src/passport/passport.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { describe, expect, test, vi, beforeEach } from "vitest"; -import { - isValidPassport, - generatePassport, - removeSymbolsFromPassport, - formatPassport, -} from "./passport"; - -describe("passport", () => { - describe("isValidPassport", () => { - describe("should return false", () => { - test("when passport is not a string", () => { - expect(isValidPassport(1 as unknown as string)).toBe(false); - }); - - test("when passport length is different from 8", () => { - expect(isValidPassport("1")).toBe(false); - }); - - test("when passport does not match the expected format", () => { - expect(isValidPassport("1112223334-")).toBe(false); - }); - }); - - describe("should return true", () => { - test("when passport is valid", () => { - expect(isValidPassport("AA111111")).toBe(true); - expect(isValidPassport("CL125167")).toBe(true); - }); - }); - }); - - describe("generatePassport", () => { - test("should always generate a valid passport", () => { - for (let i = 0; i < 10_000; i++) { - expect(isValidPassport(generatePassport())).toBe(true); - } - }); - }); - - describe("removeSymbolsFromPassport", () => { - test("when there are no symbols, returns the same string", () => { - expect(removeSymbolsFromPassport("Ab123456")).toBe("Ab123456"); - }); - - test("when there are spaces, returns the string without them", () => { - expect(removeSymbolsFromPassport(" AB 123 456 ")).toBe("AB123456"); - }); - - test("when there are dashes, returns the string without them", () => { - expect(removeSymbolsFromPassport("-AB1-23-4-56-")).toBe("AB123456"); - }); - - test("when there are dots, returns the string without them", () => { - expect(removeSymbolsFromPassport(".AB.1.23.456.")).toBe("AB123456"); - }); - - test("when there are multiple symbols, returns the string without any of them", () => { - expect(removeSymbolsFromPassport(".A B.1.2-3.45 -. 6.")).toBe("AB123456"); - }); - }); - - describe("formatPassport", () => { - beforeEach(() => { - vi.restoreAllMocks(); - }); - - test("when passport is valid, returns the formatted passport", () => { - vi.spyOn({ isValidPassport }, "isValidPassport").mockReturnValue(true); - expect(formatPassport("yz 987654")).toBe("YZ987654"); - }); - - test("when passport is not valid, returns null", () => { - vi.spyOn({ isValidPassport }, "isValidPassport").mockReturnValue(false); - expect(formatPassport("acd12736")).toBeNull(); - }); - }); -}); diff --git a/src/passport/passport.ts b/src/passport/passport.ts deleted file mode 100644 index 0a15348f..00000000 --- a/src/passport/passport.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Checks if a Brazilian passport number is valid. - * To be considered valid, the input must be a string containing exactly two - * alphabetical characters followed by exactly six numerical digits. - * This function does not verify if the input is a real passport number, - * as there are no checksums for the Brazilian passport. - * - * @param passport - The string containing the passport number to be checked. - * @returns True if the passport number is valid (2 letters followed by 6 digits). - * - * @example - * isValidPassport("Ab123456") // false - must be uppercase - * isValidPassport("AB123456") // true - * isValidPassport("12345678") // false - * isValidPassport("DC-221345") // false - */ -export const isValidPassport = (passport: string): boolean => { - if (typeof passport !== "string") return false; - return /^[A-Z]{2}[0-9]{6}$/.test(passport); -}; - -/** - * Removes symbols ('-', '.', and whitespaces) from a passport number. - * - * @param passport - The string containing a passport number. - * @returns The passport number with dashes, dots, and whitespaces removed. - * - * @example - * removeSymbolsFromPassport("Ab123456") // "Ab123456" - * removeSymbolsFromPassport("Ab-123456") // "Ab123456" - * removeSymbolsFromPassport("Ab -. 123456") // "Ab123456" - */ -export const removeSymbolsFromPassport = (passport: string): string => - passport.replace(/[\s.\-]/g, ""); - -/** - * Formats a Brazilian passport number for display. - * Returns the passport uppercased and without symbols, or null if invalid. - * - * @param passport - A Brazilian passport number (any case, possibly with symbols). - * @returns The formatted passport number (uppercase, no symbols), or null if invalid. - * - * @example - * formatPassport("Ab123456") // "AB123456" - * formatPassport("Ab-123456") // "AB123456" - * formatPassport("111111") // null - */ -export const formatPassport = (passport: string): string | null => { - const cleaned = removeSymbolsFromPassport(passport.toUpperCase()); - return isValidPassport(cleaned) ? cleaned : null; -} - -/** - * Generates a random valid Brazilian passport number. - * - * @returns A random valid passport number string (e.g. "RY393097"). - * - * @example - * generatePassport() // "RY393097" - * generatePassport() // "ZS840088" - */ -export const generatePassport = (): string => { - const letters = Array.from({ length: 2 }, () => - String.fromCharCode(65 + Math.floor(Math.random() * 26)) - ).join(""); - - const digits = Array.from({ length: 6 }, () => - Math.floor(Math.random() * 10) - ).join(""); - - return `${letters}${digits}`; -} From cbca9e6944a16598bccaaf09ac509c8448692c77 Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Wed, 1 Apr 2026 19:34:44 -0300 Subject: [PATCH 05/10] docs: add documentation for passport utilities --- docs/pt-br/utilities.md | 45 +++++++++++++++++++++++++++++++++++++++++ docs/utilities.md | 45 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/docs/pt-br/utilities.md b/docs/pt-br/utilities.md index c79abf84..c132f2e0 100644 --- a/docs/pt-br/utilities.md +++ b/docs/pt-br/utilities.md @@ -503,3 +503,48 @@ getHolidays(2024); getHolidays({ year: 2024, stateCode: 'SP' }); // Inclui feriados nacionais mais feriados estaduais (ex: "Revolução Constitucionalista") ``` + +## isValidPassport + +Verifica se um número de passaporte brasileiro é válido (2 letras maiúsculas seguidas de 6 dígitos). + +```javascript +import { isValidPassport } from '@brazilian-utils/brazilian-utils'; + +isValidPassport('AB123456'); // true +isValidPassport('Ab123456'); // false +isValidPassport('12345678'); // false +``` + +## formatPassport + +Formata um número de passaporte brasileiro (maiúsculas, sem símbolos). + +```javascript +import { formatPassport } from '@brazilian-utils/brazilian-utils'; + +formatPassport('ab123456'); // 'AB123456' +formatPassport('AB-123.456'); // 'AB123456' +formatPassport('12345678'); // null +``` + +## generatePassport + +Gera um número de passaporte brasileiro válido aleatoriamente. + +```javascript +import { generatePassport } from '@brazilian-utils/brazilian-utils'; + +generatePassport(); // 'RY393097' +``` + +## parsePassport + +Remove símbolos ('-', '.' e espaços) de um número de passaporte. + +```javascript +import { parsePassport } from '@brazilian-utils/brazilian-utils'; + +parsePassport('AB-123.456'); // 'AB123456' +parsePassport(' AB 123 456 '); // 'AB123456' +``` diff --git a/docs/utilities.md b/docs/utilities.md index 54476a2d..39882b27 100644 --- a/docs/utilities.md +++ b/docs/utilities.md @@ -503,3 +503,48 @@ getHolidays(2024); getHolidays({ year: 2024, stateCode: 'SP' }); // Includes national holidays plus state-specific holidays (e.g., "Revolução Constitucionalista") ``` + +## isValidPassport + +Check if a Brazilian passport number is valid (2 uppercase letters followed by 6 digits). + +```javascript +import { isValidPassport } from '@brazilian-utils/brazilian-utils'; + +isValidPassport('AB123456'); // true +isValidPassport('Ab123456'); // false +isValidPassport('12345678'); // false +``` + +## formatPassport + +Format a Brazilian passport number (uppercase, without symbols). + +```javascript +import { formatPassport } from '@brazilian-utils/brazilian-utils'; + +formatPassport('ab123456'); // 'AB123456' +formatPassport('AB-123.456'); // 'AB123456' +formatPassport('12345678'); // null +``` + +## generatePassport + +Generate a random valid Brazilian passport number. + +```javascript +import { generatePassport } from '@brazilian-utils/brazilian-utils'; + +generatePassport(); // 'RY393097' +``` + +## parsePassport + +Remove symbols ('-', '.', and whitespaces) from a passport number. + +```javascript +import { parsePassport } from '@brazilian-utils/brazilian-utils'; + +parsePassport('AB-123.456'); // 'AB123456' +parsePassport(' AB 123 456 '); // 'AB123456' +``` From f4dced022c2249d5f38d9e9ccca3216549dc23b1 Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Fri, 3 Apr 2026 13:41:48 -0300 Subject: [PATCH 06/10] refactor: apply code review suggestions for passport module --- docs/pt-br/utilities.md | 1 - docs/utilities.md | 1 - src/format-passport/format-passport.test.ts | 27 +++++++------------ src/format-passport/format-passport.ts | 19 ++++++------- src/generate-passport/constants.ts | 4 +++ .../generate-passport.test.ts | 10 +++---- src/generate-passport/generate-passport.ts | 15 ++++++----- src/is-valid-passport/constants.ts | 1 + src/is-valid-passport/is-valid-passport.ts | 8 +++--- src/parse-passport/parse-passport.ts | 6 +++-- 10 files changed, 44 insertions(+), 48 deletions(-) create mode 100644 src/generate-passport/constants.ts create mode 100644 src/is-valid-passport/constants.ts diff --git a/docs/pt-br/utilities.md b/docs/pt-br/utilities.md index c132f2e0..4e8ccbc1 100644 --- a/docs/pt-br/utilities.md +++ b/docs/pt-br/utilities.md @@ -525,7 +525,6 @@ import { formatPassport } from '@brazilian-utils/brazilian-utils'; formatPassport('ab123456'); // 'AB123456' formatPassport('AB-123.456'); // 'AB123456' -formatPassport('12345678'); // null ``` ## generatePassport diff --git a/docs/utilities.md b/docs/utilities.md index 39882b27..49e4cbd1 100644 --- a/docs/utilities.md +++ b/docs/utilities.md @@ -525,7 +525,6 @@ import { formatPassport } from '@brazilian-utils/brazilian-utils'; formatPassport('ab123456'); // 'AB123456' formatPassport('AB-123.456'); // 'AB123456' -formatPassport('12345678'); // null ``` ## generatePassport diff --git a/src/format-passport/format-passport.test.ts b/src/format-passport/format-passport.test.ts index a4cafef6..c37fc11f 100644 --- a/src/format-passport/format-passport.test.ts +++ b/src/format-passport/format-passport.test.ts @@ -1,23 +1,14 @@ -import { beforeEach, describe, expect, test, vi } from "vitest"; -import { isValidPassport } from "../is-valid-passport/is-valid-passport"; +import { describe, expect, test } from "vitest"; import { formatPassport } from "./format-passport"; describe("formatPassport", () => { - beforeEach(() => { - vi.restoreAllMocks(); - }); + describe("should return the formatted passport", () => { + test("when passport is valid", () => { + expect(formatPassport("AB123456")).toBe("AB123456"); + }); - describe("should return the formatted passport", () => { - test("when passport is valid", () => { - vi.spyOn({ isValidPassport }, "isValidPassport").mockReturnValue(true); - expect(formatPassport("yz 987654")).toBe("YZ987654"); - }); - }); - - describe("should return null", () => { - test("when passport is not valid", () => { - vi.spyOn({ isValidPassport }, "isValidPassport").mockReturnValue(false); - expect(formatPassport("acd12736")).toBeNull(); - }); - }); + test("when passport has lowercase letters", () => { + expect(formatPassport("acd12736")).toBe("ACD12736"); + }); + }); }); diff --git a/src/format-passport/format-passport.ts b/src/format-passport/format-passport.ts index 10c3ac27..fb99ceac 100644 --- a/src/format-passport/format-passport.ts +++ b/src/format-passport/format-passport.ts @@ -1,19 +1,16 @@ -import { isValidPassport } from "../is-valid-passport/is-valid-passport"; -import { parsePassport } from "../parse-passport/parse-passport"; - /** * Formats a Brazilian passport number for display. - * Returns the passport uppercased and without symbols, or null if invalid. + * Converts to uppercase and removes all non-alphanumeric characters. * * @param passport - A Brazilian passport number (any case, possibly with symbols). - * @returns The formatted passport number (uppercase, no symbols), or null if invalid. + * @returns The formatted passport number (uppercase, no symbols), or an empty string if invalid. * * @example - * formatPassport("Ab123456") // "AB123456" - * formatPassport("Ab-123456") // "AB123456" - * formatPassport("111111") // null + * formatPassport("ab123456") // "AB123456" + * formatPassport("AB-123.456") // "AB123456" + * formatPassport("") // "" */ -export const formatPassport = (passport: string): string | null => { - const cleaned = parsePassport(passport.toUpperCase()); - return isValidPassport(cleaned) ? cleaned : null; +export const formatPassport = (passport: string): string => { + if (!passport || typeof passport !== "string") return ""; + return passport.toUpperCase().replace(/[^A-Z0-9]/g, ""); }; diff --git a/src/generate-passport/constants.ts b/src/generate-passport/constants.ts new file mode 100644 index 00000000..d5f7ba3e --- /dev/null +++ b/src/generate-passport/constants.ts @@ -0,0 +1,4 @@ +export const LETTERS_LENGTH = 2; +export const DIGITS_LENGTH = 6; +export const ALPHABET_LENGTH = 26; +export const CHAR_CODE_A = 65; diff --git a/src/generate-passport/generate-passport.test.ts b/src/generate-passport/generate-passport.test.ts index b5eb85d6..e87ebb2b 100644 --- a/src/generate-passport/generate-passport.test.ts +++ b/src/generate-passport/generate-passport.test.ts @@ -3,9 +3,9 @@ import { isValidPassport } from "../is-valid-passport/is-valid-passport"; import { generatePassport } from "./generate-passport"; describe("generatePassport", () => { - test("should always generate a valid passport", () => { - for (let i = 0; i < 10_000; i++) { - expect(isValidPassport(generatePassport())).toBe(true); - } - }); + test("should always generate a valid passport", () => { + for (let i = 0; i < 100; i++) { + expect(isValidPassport(generatePassport())).toBe(true); + } + }); }); diff --git a/src/generate-passport/generate-passport.ts b/src/generate-passport/generate-passport.ts index e2306b89..9c959078 100644 --- a/src/generate-passport/generate-passport.ts +++ b/src/generate-passport/generate-passport.ts @@ -1,3 +1,6 @@ +import { generateRandomNumber } from "../_internals/generate-random-number/generate-random-number"; +import { LETTERS_LENGTH, DIGITS_LENGTH, ALPHABET_LENGTH, CHAR_CODE_A } from "./constants"; + /** * Generates a random valid Brazilian passport number. * @@ -8,13 +11,11 @@ * generatePassport() // "ZS840088" */ export const generatePassport = (): string => { - const letters = Array.from({ length: 2 }, () => - String.fromCharCode(65 + Math.floor(Math.random() * 26)), - ).join(""); + const letters = Array.from({ length: LETTERS_LENGTH }, () => + String.fromCharCode(CHAR_CODE_A + Math.floor(Math.random() * ALPHABET_LENGTH)), + ).join(""); - const digits = Array.from({ length: 6 }, () => - Math.floor(Math.random() * 10), - ).join(""); + const digits = generateRandomNumber(DIGITS_LENGTH); - return `${letters}${digits}`; + return `${letters}${digits}`; }; diff --git a/src/is-valid-passport/constants.ts b/src/is-valid-passport/constants.ts new file mode 100644 index 00000000..24026f84 --- /dev/null +++ b/src/is-valid-passport/constants.ts @@ -0,0 +1 @@ +export const PASSPORT_REGEX = /^[A-Z]{2}[0-9]{6}$/; diff --git a/src/is-valid-passport/is-valid-passport.ts b/src/is-valid-passport/is-valid-passport.ts index 60b8ff6a..13b2a46d 100644 --- a/src/is-valid-passport/is-valid-passport.ts +++ b/src/is-valid-passport/is-valid-passport.ts @@ -1,3 +1,5 @@ +import { PASSPORT_REGEX } from "./constants"; + /** * Checks if a Brazilian passport number is valid. * To be considered valid, the input must be a string containing exactly two @@ -14,7 +16,7 @@ * isValidPassport("12345678") // false * isValidPassport("DC-221345") // false */ -export const isValidPassport = (passport: string): boolean => { - if (typeof passport !== "string") return false; - return /^[A-Z]{2}[0-9]{6}$/.test(passport); +export const isValidPassport = (passport: string | number): boolean => { + if (passport === null || passport === undefined) return false; + return PASSPORT_REGEX.test(String(passport)); }; diff --git a/src/parse-passport/parse-passport.ts b/src/parse-passport/parse-passport.ts index a382f931..0b588076 100644 --- a/src/parse-passport/parse-passport.ts +++ b/src/parse-passport/parse-passport.ts @@ -9,5 +9,7 @@ * parsePassport("Ab-123456") // "Ab123456" * parsePassport("Ab -. 123456") // "Ab123456" */ -export const parsePassport = (passport: string): string => - passport.replace(/[\s.-]/g, ""); +export const parsePassport = (passport: string): string => { + if (!passport || typeof passport !== "string") return ""; + return passport.replace(/[^a-zA-Z0-9]/g, ""); +}; From 86e37cff70020298b237eee2d9ea7cb793daca7e Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Mon, 6 Apr 2026 19:09:26 -0300 Subject: [PATCH 07/10] fix: lint --- src/format-passport/format-passport.test.ts | 16 ++++++++-------- .../generate-passport.test.ts | 10 +++++----- src/generate-passport/generate-passport.ts | 19 +++++++++++++------ src/is-valid-passport/is-valid-passport.ts | 4 ++-- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/format-passport/format-passport.test.ts b/src/format-passport/format-passport.test.ts index c37fc11f..b4eaa8b6 100644 --- a/src/format-passport/format-passport.test.ts +++ b/src/format-passport/format-passport.test.ts @@ -2,13 +2,13 @@ import { describe, expect, test } from "vitest"; import { formatPassport } from "./format-passport"; describe("formatPassport", () => { - describe("should return the formatted passport", () => { - test("when passport is valid", () => { - expect(formatPassport("AB123456")).toBe("AB123456"); - }); + describe("should return the formatted passport", () => { + test("when passport is valid", () => { + expect(formatPassport("AB123456")).toBe("AB123456"); + }); - test("when passport has lowercase letters", () => { - expect(formatPassport("acd12736")).toBe("ACD12736"); - }); - }); + test("when passport has lowercase letters", () => { + expect(formatPassport("acd12736")).toBe("ACD12736"); + }); + }); }); diff --git a/src/generate-passport/generate-passport.test.ts b/src/generate-passport/generate-passport.test.ts index e87ebb2b..d050c2f8 100644 --- a/src/generate-passport/generate-passport.test.ts +++ b/src/generate-passport/generate-passport.test.ts @@ -3,9 +3,9 @@ import { isValidPassport } from "../is-valid-passport/is-valid-passport"; import { generatePassport } from "./generate-passport"; describe("generatePassport", () => { - test("should always generate a valid passport", () => { - for (let i = 0; i < 100; i++) { - expect(isValidPassport(generatePassport())).toBe(true); - } - }); + test("should always generate a valid passport", () => { + for (let i = 0; i < 100; i++) { + expect(isValidPassport(generatePassport())).toBe(true); + } + }); }); diff --git a/src/generate-passport/generate-passport.ts b/src/generate-passport/generate-passport.ts index 9c959078..85bed750 100644 --- a/src/generate-passport/generate-passport.ts +++ b/src/generate-passport/generate-passport.ts @@ -1,5 +1,10 @@ import { generateRandomNumber } from "../_internals/generate-random-number/generate-random-number"; -import { LETTERS_LENGTH, DIGITS_LENGTH, ALPHABET_LENGTH, CHAR_CODE_A } from "./constants"; +import { + ALPHABET_LENGTH, + CHAR_CODE_A, + DIGITS_LENGTH, + LETTERS_LENGTH, +} from "./constants"; /** * Generates a random valid Brazilian passport number. @@ -11,11 +16,13 @@ import { LETTERS_LENGTH, DIGITS_LENGTH, ALPHABET_LENGTH, CHAR_CODE_A } from "./c * generatePassport() // "ZS840088" */ export const generatePassport = (): string => { - const letters = Array.from({ length: LETTERS_LENGTH }, () => - String.fromCharCode(CHAR_CODE_A + Math.floor(Math.random() * ALPHABET_LENGTH)), - ).join(""); + const letters = Array.from({ length: LETTERS_LENGTH }, () => + String.fromCharCode( + CHAR_CODE_A + Math.floor(Math.random() * ALPHABET_LENGTH), + ), + ).join(""); - const digits = generateRandomNumber(DIGITS_LENGTH); + const digits = generateRandomNumber(DIGITS_LENGTH); - return `${letters}${digits}`; + return `${letters}${digits}`; }; diff --git a/src/is-valid-passport/is-valid-passport.ts b/src/is-valid-passport/is-valid-passport.ts index 13b2a46d..bcbaad1d 100644 --- a/src/is-valid-passport/is-valid-passport.ts +++ b/src/is-valid-passport/is-valid-passport.ts @@ -17,6 +17,6 @@ import { PASSPORT_REGEX } from "./constants"; * isValidPassport("DC-221345") // false */ export const isValidPassport = (passport: string | number): boolean => { - if (passport === null || passport === undefined) return false; - return PASSPORT_REGEX.test(String(passport)); + if (passport === null || passport === undefined) return false; + return PASSPORT_REGEX.test(String(passport)); }; From 14d186e2cb08648a7f0fb64daffa6c6367d7902e Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Mon, 6 Apr 2026 21:05:24 -0300 Subject: [PATCH 08/10] chore: rebase and resolve conflicts with main --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index c91d2138..05fe26f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,10 +53,10 @@ export { parseCep } from "./parse-cep/parse-cep"; export { parseCnpj } from "./parse-cnpj/parse-cnpj"; export { parseCpf } from "./parse-cpf/parse-cpf"; export { parseCurrency } from "./parse-currency/parse-currency"; +export { parsePassport } from "./parse-passport/parse-passport"; export { parsePhone } from "./parse-phone/parse-phone"; export { parsePis } from "./parse-pis/parse-pis"; export { parseProcessoJuridico } from "./parse-processo-juridico/parse-processo-juridico"; -export { parsePassport } from "./parse-passport/parse-passport"; // ============================================================================ // DEPRECATED EXPORTS - Will be removed in v3.0.0 From ca8c368ecfe38273b94ce6c7201323cda2419f6d Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Tue, 7 Apr 2026 10:38:14 -0300 Subject: [PATCH 09/10] fix: passport tests --- src/format-passport/format-passport.test.ts | 2 +- src/generate-passport/generate-passport.test.ts | 2 +- src/is-valid-passport/is-valid-passport.test.ts | 2 +- src/parse-passport/parse-passport.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/format-passport/format-passport.test.ts b/src/format-passport/format-passport.test.ts index b4eaa8b6..08a73ae3 100644 --- a/src/format-passport/format-passport.test.ts +++ b/src/format-passport/format-passport.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "../_internals/test/runtime"; import { formatPassport } from "./format-passport"; describe("formatPassport", () => { diff --git a/src/generate-passport/generate-passport.test.ts b/src/generate-passport/generate-passport.test.ts index d050c2f8..beb50104 100644 --- a/src/generate-passport/generate-passport.test.ts +++ b/src/generate-passport/generate-passport.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "../_internals/test/runtime"; import { isValidPassport } from "../is-valid-passport/is-valid-passport"; import { generatePassport } from "./generate-passport"; diff --git a/src/is-valid-passport/is-valid-passport.test.ts b/src/is-valid-passport/is-valid-passport.test.ts index 439954e1..afd50933 100644 --- a/src/is-valid-passport/is-valid-passport.test.ts +++ b/src/is-valid-passport/is-valid-passport.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "../_internals/test/runtime"; import { isValidPassport } from "./is-valid-passport"; describe("isValidPassport", () => { diff --git a/src/parse-passport/parse-passport.test.ts b/src/parse-passport/parse-passport.test.ts index ef6800b1..c7e0f0d2 100644 --- a/src/parse-passport/parse-passport.test.ts +++ b/src/parse-passport/parse-passport.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, test } from "../_internals/test/runtime"; import { parsePassport } from "./parse-passport"; describe("parsePassport", () => { From da4e35665afd81cf8cd6ad7cae0ee1a40a322b38 Mon Sep 17 00:00:00 2001 From: Vicente Vendramin Date: Tue, 7 Apr 2026 11:14:20 -0300 Subject: [PATCH 10/10] fix: lint --- src/generate-passport/generate-passport.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/generate-passport/generate-passport.ts b/src/generate-passport/generate-passport.ts index 85bed750..5967c6fd 100644 --- a/src/generate-passport/generate-passport.ts +++ b/src/generate-passport/generate-passport.ts @@ -1,10 +1,5 @@ import { generateRandomNumber } from "../_internals/generate-random-number/generate-random-number"; -import { - ALPHABET_LENGTH, - CHAR_CODE_A, - DIGITS_LENGTH, - LETTERS_LENGTH, -} from "./constants"; +import { ALPHABET_LENGTH, CHAR_CODE_A, DIGITS_LENGTH, LETTERS_LENGTH } from "./constants"; /** * Generates a random valid Brazilian passport number. @@ -17,9 +12,7 @@ import { */ export const generatePassport = (): string => { const letters = Array.from({ length: LETTERS_LENGTH }, () => - String.fromCharCode( - CHAR_CODE_A + Math.floor(Math.random() * ALPHABET_LENGTH), - ), + String.fromCharCode(CHAR_CODE_A + Math.floor(Math.random() * ALPHABET_LENGTH)), ).join(""); const digits = generateRandomNumber(DIGITS_LENGTH);