From 310730e13be3ae8d881775565484845348a7746f Mon Sep 17 00:00:00 2001 From: FabioRolin Date: Thu, 18 Jun 2026 14:33:16 -0300 Subject: [PATCH] feat: add stripCpfCnpj (alphanumeric-safe CPF/CNPJ sanitizer) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expõe um export público `stripCpfCnpj` que remove os separadores da máscara e normaliza para upper-case PRESERVANDO as letras do CNPJ alfanumérico (RFB NT 49/2024). Delega ao `stripAlphanumeric` interno (já usado/testado por maskValue, normalizeCpfOrCnpj e formatCnpj), evitando duplicar a regra. Motivação: hoje os MFEs usam `stripNumbers`/`value.replace(/\D/g,'')` para limpar CNPJ, o que descarta as letras A–Z e quebra CNPJ alfanumérico a partir de 2026. Como não havia um *strip* alfanumérico-safe público (stripNumbers é destrutivo; normalizeCpfOrCnpj aplica máscara, o inverso), cada MFE criou um helper local. Este export centraliza a regra em um único ponto de atualização, conforme stack/alphanumeric-cnpj-stripping.md (§2), e permite remover os helpers duplicados nos MFEs. stripCpfCnpj é o inverso de normalizeCpfOrCnpj. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/index.ts | 1 + src/utils/stripCpfCnpj.ts | 25 +++++++++++++++++++++++++ tests/stripCpfCnpj.spec.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 src/utils/stripCpfCnpj.ts create mode 100644 tests/stripCpfCnpj.spec.ts diff --git a/src/index.ts b/src/index.ts index 1a01659..aa6d483 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ export { default as normalizeSocialNumber } from './normalizers/normalizeSocialN // utils export { default as maskString } from './utils/maskString'; export { default as stripNumbers } from './utils/stripNumbers'; +export { default as stripCpfCnpj } from './utils/stripCpfCnpj'; export { default as getStates } from './utils/getStates'; export { default as snakeToCamel } from './utils/snakeToCamel'; export { default as camelToSnake } from './utils/camelToSnake'; diff --git a/src/utils/stripCpfCnpj.ts b/src/utils/stripCpfCnpj.ts new file mode 100644 index 0000000..a40a379 --- /dev/null +++ b/src/utils/stripCpfCnpj.ts @@ -0,0 +1,25 @@ +import { stripAlphanumeric } from '../masks/strip'; + +/** + * Sanitiza um CPF/CNPJ removendo os separadores da máscara (qualquer caractere + * não-alfanumérico: `.`, `/`, `-`, espaços) e normalizando para upper-case, + * **preservando as letras** do CNPJ alfanumérico (Receita Federal — + * Nota Técnica NT 49/2024, vigente a partir de 2026). + * + * Use no lugar de `stripNumbers` / `value.replace(/\D/g, '')` em qualquer fluxo + * de CNPJ (validação, envio, lookup, navegação): essas abordagens descartam as + * letras A–Z e transformam um CNPJ alfanumérico válido em um identificador + * inválido. + * + * É o inverso de {@link normalizeCpfOrCnpj} (que aplica a máscara visual). + * + * @example + * stripCpfCnpj('824.007.050-70'); // '82400705070' (CPF) + * stripCpfCnpj('12.345.678/0001-95'); // '12345678000195' (CNPJ numérico) + * stripCpfCnpj('AB.345.678/XY01-74'); // 'AB345678XY0174' (CNPJ alfanumérico) + * stripCpfCnpj('ab.345.678/xy01-74'); // 'AB345678XY0174' (normaliza upper-case) + * stripCpfCnpj(undefined); // '' + */ +export default function stripCpfCnpj(value?: string): string { + return stripAlphanumeric(value ?? ''); +} diff --git a/tests/stripCpfCnpj.spec.ts b/tests/stripCpfCnpj.spec.ts new file mode 100644 index 0000000..3a5cf59 --- /dev/null +++ b/tests/stripCpfCnpj.spec.ts @@ -0,0 +1,32 @@ +import 'jest'; + +import { stripCpfCnpj } from '../src'; + +describe('stripCpfCnpj', () => { + test('CPF mascarado → apenas dígitos', () => { + expect(stripCpfCnpj('824.007.050-70')).toBe('82400705070'); + }); + + test('CNPJ numérico mascarado → apenas dígitos', () => { + expect(stripCpfCnpj('12.345.678/0001-95')).toBe('12345678000195'); + }); + + // RFB NT 49/2024 — as letras fazem parte do identificador e não podem ser + // descartadas (o que aconteceria com stripNumbers / /\D/g). + test('CNPJ alfanumérico preserva as letras', () => { + expect(stripCpfCnpj('AB.345.678/XY01-74')).toBe('AB345678XY0174'); + }); + + test('CNPJ alfanumérico minúsculo é normalizado para upper-case', () => { + expect(stripCpfCnpj('ab.345.678/xy01-74')).toBe('AB345678XY0174'); + }); + + test('valor já sem máscara é idempotente', () => { + expect(stripCpfCnpj('AB345678XY0174')).toBe('AB345678XY0174'); + }); + + test('undefined / vazio → string vazia', () => { + expect(stripCpfCnpj(undefined)).toBe(''); + expect(stripCpfCnpj('')).toBe(''); + }); +});