diff --git a/docs/utils/cpfValidator.md b/docs/utils/cpfValidator.md new file mode 100644 index 0000000..6eabd55 --- /dev/null +++ b/docs/utils/cpfValidator.md @@ -0,0 +1,74 @@ +# CpfValidator + +Utilitário para validar CPFs com e sem máscara. + +## Instalação e Importação + +```typescript +import { cpfValidator } from '@sysvale/foundry'; +``` + +## Função + +### `cpfValidator()` + +Valida CPFs com e sem máscara, indicando se os mesmos são válidos. + +#### Sintaxes + +```typescript +cpfValidator(value: string): boolean +``` + +#### Parâmetros + +**Assinatura 1:** + +- **`value`** (`string`): CPF (com ou sem máscara) a ser validado + +
+ +#### Retorno + +`boolean` - Resultado da validação, `true` para CPF válido e `false` para inválido + +
+ +#### Exemplos + +**Usando CPF com máscara:** + +```typescript +cpfValidator('252.512.510-09'); // → true + +cpfValidator('000.000.000-00'); // → false +``` + +
+ +**Usando CPF sem máscara:** + +```typescript +cpfValidator('25251251009'); // → true + +cpfValidator('00000000000'); // → false +``` + +
+ +#### Tratamento de Erros + +A função lança um erro quando os parâmetros obrigatórios não são fornecidos: + +```typescript +// ❌ Erro: tipagem do parâmetro é inválida +cpfValidator(71234567823); +// → Error: O tipo do parâmetro passado é inválido. + +// ✅ Correto +cpfValidator('71234567823'); // → false +``` + +## Notas + +- A função é **type-safe** diff --git a/docs/utils/index.md b/docs/utils/index.md index 6230e10..c15b6b9 100644 --- a/docs/utils/index.md +++ b/docs/utils/index.md @@ -17,3 +17,9 @@ Função para formatar listas de strings com vírgulas e conjunção "e". Função para sanitizar dados de formulário e aplicar transformações antes de enviá-los ao backend. - [Documentação](./sanitizeForm.md) + +### cpfValidator() + +Função para validar CPFs com e sem máscara. + +- [Documentação](./cpfValidator.md) diff --git a/package-lock.json b/package-lock.json index e9a6e8a..ca94521 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sysvale/foundry", - "version": "1.0.0", + "version": "1.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sysvale/foundry", - "version": "1.0.0", + "version": "1.5.1", "license": "Apache-2.0", "devDependencies": { "@eslint/js": "^9.32.0", diff --git a/package.json b/package.json index 363141f..9ce040e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sysvale/foundry", - "version": "1.5.1", + "version": "1.6.0", "description": "A forge for composables, helpers, and front-end utilities.", "type": "module", "main": "./dist/foundry.cjs.js", diff --git a/src/index.ts b/src/index.ts index 49cfc76..d4501ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from './utils/pluralize'; export * from './utils/commaline'; export * from './utils/sanitizeForm'; +export * from './utils/cpfValidator'; export { maskCpf, removeCpfMask } from './formatters/cpf'; export { maskCns, removeCnsMask } from './formatters/cns'; export { maskPhone, removePhoneMask } from './formatters/phone'; diff --git a/src/utils/cpfValidator.ts b/src/utils/cpfValidator.ts new file mode 100644 index 0000000..9ba8842 --- /dev/null +++ b/src/utils/cpfValidator.ts @@ -0,0 +1,65 @@ +function calcChecker1(firstNineDigits: string) { + let sum = 0; + + for (let j = 0; j < 9; ++j) { + sum += Number(firstNineDigits.charAt(j)) * (10 - j); + } + + const lastSumChecker1 = sum % 11; + const checker1 = lastSumChecker1 < 2 ? 0 : 11 - lastSumChecker1; + + return checker1; +} + +function calcChecker2(cpfWithChecker1: string) { + let sum = 0; + + for (let k = 0; k < 10; ++k) { + sum += Number(cpfWithChecker1.charAt(k)) * (11 - k); + } + const lastSumChecker2 = sum % 11; + const checker2 = lastSumChecker2 < 2 ? 0 : 11 - lastSumChecker2; + + return checker2; +} + +function cleaner(value: string) { + return value.replace(/[^\d]/g, ''); +} + +/** + * Valida CPFs com e sem máscara. + * + * @param { string } value + * @returns { boolean } + */ +export function cpfValidator(value: string): boolean { + if (typeof value !== 'string') { + throw new Error('O tipo do parâmetro passado é inválido.'); + } + + if (!value) { + return true; + } + + const cleanCPF = cleaner(value); + const firstNineDigits = cleanCPF.substring(0, 9); + const checker = cleanCPF.substring(9, 11); + let result = false; + + for (let i = 0; i < 10; i++) { + if (firstNineDigits + checker === Array(12).join(i.toString())) { + return false; + } + } + + const checker1 = calcChecker1(firstNineDigits); + const checker2 = calcChecker2(`${firstNineDigits}${checker1}`); + + if (checker.toString() === checker1.toString() + checker2.toString()) { + result = true; + } else { + result = false; + } + return result; +} diff --git a/tests/cpfValidator.test.ts b/tests/cpfValidator.test.ts new file mode 100644 index 0000000..e15e750 --- /dev/null +++ b/tests/cpfValidator.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from 'vitest'; +import { cpfValidator } from '../src/utils/cpfValidator'; + +describe('cpfValidator()', () => { + test('retorna true quando string vazia é passada', () => { + expect(cpfValidator('')).toBe(true); + }); + + test('retorna false quando cpf inválido com máscara é passado', () => { + expect(cpfValidator('111.111.111-11')).toBe(false); + }); + + test('retorna true quando cpf válido com máscara é passado', () => { + expect(cpfValidator('252.512.510-09')).toBe(true); + }); + + test('retorna false quando cpf inválido sem máscara é passado', () => { + expect(cpfValidator('11111111111')).toBe(false); + }); + + test('retorna true quando cpf válido sem máscara é passado', () => { + expect(cpfValidator('25251251009')).toBe(true); + }); + + test('retorna false quando cpf possui menos que 11 dígitos', () => { + expect(cpfValidator('2525125100')).toBe(false); + }); + + test('retorna false quando cpf possui uma letra', () => { + expect(cpfValidator('25251251a09')).toBe(false); + }); + + test('lança erro quando parâmetro é do tipo number', () => { + expect(() => cpfValidator(12341789324)).toThrowError(); + }); +});