Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ on:
pull_request:
branches: [ main ]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ on:
pull_request:
branches: [ main ]

concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout repository
Expand Down
19 changes: 13 additions & 6 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ pnpm add validations-br

## useValidationsBR

### Parâmetros
### Parameters

| Key | Type | Description |
| :---- | :--------------------------------------------------: | ---------------------------: |
| type | `cnpj \| cpf \| cep \| email \| pis \| phone \| uf` | Type of data to be validated |
| type | `cnpj \| cpf \| cnh \| cep \| email \| pis \| phone \| uf` | Type of data to be validated |
| value | `string` | Value that will be validated |

```js
Expand All @@ -67,6 +67,13 @@ const cnpj = '09.015.844/0001-80';
const isValid = validateCNPJ(cnpj);
```

The new **alphanumeric** CNPJ format from Receita Federal is also supported
(12 uppercase alphanumeric positions + 2 numeric check digits):

```js
validateCNPJ('12.ABC.345/01DE-35'); // true
```

## Validate CPF

```js
Expand Down Expand Up @@ -107,7 +114,7 @@ const phone = '(14) 99767-9472';
const isValid = validatePhone(phone);
```

## Validar CNH
## Validate CNH

```js
import { validateCNH } from 'validations-br';
Expand All @@ -120,11 +127,11 @@ const isValid = validateCNH(cnh);
## Validate CEP

```js
import { validateCEP } from 'validations-br';
// const { validateCEP } = require('validations-br');
import { validateCep } from 'validations-br';
// const { validateCep } = require('validations-br');

const cep = '17280-000';
const isValid = validateCEP(cep);
const isValid = validateCep(cep);
```

## Validate UF
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pnpm add validations-br

| Chave | Tipo | Descrição |
| :---- | :--------------------------------------------------: | --------------------------------: |
| type | `cnpj \| cpf \| cep \| email \| pis \| phone \| uf` | Tipo de dados que serão validados |
| type | `cnpj \| cpf \| cnh \| cep \| email \| pis \| phone \| uf` | Tipo de dados que serão validados |
| value | `string` | Valor que será validado |

```js
Expand All @@ -68,6 +68,13 @@ const cnpj = '09.015.844/0001-80';
const isValid = validateCNPJ(cnpj);
```

Também há suporte ao novo formato **alfanumérico** de CNPJ da Receita Federal
(12 posições alfanuméricas em maiúsculas + 2 dígitos verificadores numéricos):

```js
validateCNPJ('12.ABC.345/01DE-35'); // true
```

## Validar CPF

```js
Expand Down Expand Up @@ -121,11 +128,11 @@ const isValid = validatePhone(phone);
## Validar CEP

```js
import { validateCEP } from 'validations-br';
// const { validateCEP } = require('validations-br');
import { validateCep } from 'validations-br';
// const { validateCep } = require('validations-br');

const cep = '17280-000';
const isValid = validateCEP(cep);
const isValid = validateCep(cep);
```

## Validar UF
Expand Down
31 changes: 28 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,31 @@
"name": "validations-br",
"version": "1.6.1",
"packageManager": "pnpm@11.9.0",
"description": "A validator to BR informations",
"description": "Validators for Brazilian documents (CPF, CNPJ, CNH, PIS, CEP, IE, phone, UF, email)",
"main": "lib/index.cjs",
"module": "lib/index.mjs",
"types": "lib/index.d.ts",
"repository": "https://github.com/reactivando/validations-br.git",
"types": "lib/index.d.cts",
"exports": {
".": {
"import": {
"types": "./lib/index.d.mts",
"default": "./lib/index.mjs"
},
"require": {
"types": "./lib/index.d.cts",
"default": "./lib/index.cjs"
}
},
"./package.json": "./package.json"
},
"repository": {
"type": "git",
"url": "git+https://github.com/reactivando/validations-br.git"
},
"homepage": "https://github.com/reactivando/validations-br#readme",
"bugs": {
"url": "https://github.com/reactivando/validations-br/issues"
},
"author": "Reactivando <reactivando2@gmail.com>",
"keywords": [
"BR",
Expand All @@ -17,6 +36,12 @@
"license": "MIT",
"type": "module",
"sideEffects": false,
"engines": {
"node": ">=18"
},
"publishConfig": {
"provenance": true
},
"files": [
"lib/**/*"
],
Expand Down
15 changes: 14 additions & 1 deletion src/__tests__/usevalidationsbr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('UseValidationBr Pis', () => {
});
});

describe('UseValidationBr Pis', () => {
describe('UseValidationBr UF', () => {
it('should be able return true to valid UF', () => {
expect(useValidationsBR('uf', 'SP')).toBe(true);
});
Expand All @@ -126,3 +126,16 @@ describe('UseValidationBr Pis', () => {
expect(useValidationsBR('uf', 'AA')).toBe(false);
});
});

describe('UseValidationBr CNH', () => {
it('should be able return true to valid CNH', () => {
expect(useValidationsBR('cnh', '134621966-24')).toBe(true);
});

it('should be able return false to invalid CNH', () => {
expect.assertions(2);

expect(useValidationsBR('cnh', '134621966-25')).toBe(false);
expect(useValidationsBR('cnh', '11111111111')).toBe(false);
});
});
7 changes: 7 additions & 0 deletions src/__tests__/validateCNH.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import { validateCNH } from '../validations/validateCNH';
describe('validateCNH', () => {
it('should return true for valid CNHs', () => {
expect(validateCNH('13462196624')).toBe(true);
// 64644399109 exercises the exceptional check-digit branches
// (dv1 >= 10 -> dsc=2, dv2 >= 10 -> 0, dv2 < 0 -> +11); the others are
// additional valid samples.
expect(validateCNH('64644399109')).toBe(true);
expect(validateCNH('41835138958')).toBe(true);
expect(validateCNH('79886050534')).toBe(true);
expect(validateCNH('58879358643')).toBe(true);
});

it('should return false for invalid CNHs', () => {
Expand Down
7 changes: 7 additions & 0 deletions src/__tests__/validateEmail.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ describe('validateEmail', () => {
expect(validateEmail('email@email.co.uk')).toBe(true);
expect(validateEmail('email.test@email.com')).toBe(true);
expect(validateEmail('email_test@email.com')).toBe(true);
expect(validateEmail('user@domain.photography')).toBe(true);
expect(validateEmail('user@domain.international')).toBe(true);
});

it('should return false for non-string inputs', () => {
expect(validateEmail(null as unknown as string)).toBe(false);
expect(validateEmail(undefined as unknown as string)).toBe(false);
});

it('should return false for invalid emails', () => {
Expand Down
37 changes: 37 additions & 0 deletions src/__tests__/validateIE.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,41 @@ describe('validateIE', () => {
expect(validateIE('2703591093-88', 'to')).toBe(false);
});
});

describe('edge cases and check-digit clamp branches', () => {
// For these states a check digit of 0 can only result from the
// "remainder produced a digit >= 10, clamp to 0" branch, so these
// valid IEs exercise that otherwise-untested path.
it('should validate IEs whose check digit is clamped to 0', () => {
expect(validateIE('46665520', 'rj')).toBe(true);
expect(validateIE('7612174970', 'rs')).toBe(true);
expect(validateIE('11182758180', 'mt')).toBe(true);
expect(validateIE('1915451760', 'pr')).toBe(true);
expect(validateIE('8211755960110', 'mg')).toBe(true);
expect(validateIE('234682540', 'pe')).toBe(true);
expect(validateIE('565343050', 'al')).toBe(true);
expect(validateIE('100060030', 'go')).toBe(true);
expect(validateIE('20809775296850', 'ro')).toBe(true);
});

it('should validate alternative IE formats', () => {
expect(validateIE('2087728666', 'rn')).toBe(true); // 10-digit RN
expect(validateIE('2019640180', 'rn')).toBe(true); // 10-digit RN, DV clamped to 0
expect(validateIE('577972880', 'to')).toBe(true); // 9-digit TO
});

it('should validate IEs that hit special-range / low-sum branches', () => {
expect(validateIE('000100102', 'am')).toBe(true); // AM weighted sum < 11
expect(validateIE('030098594', 'ap')).toBe(true); // AP body range 3.000.001-3.017.000
expect(validateIE('030176168', 'ap')).toBe(true); // AP body range 3.017.001-3.019.022
expect(validateIE('101182660', 'go')).toBe(true); // GO body range 10.103.105-10.119.997
});

it('should reject IEs with invalid prefixes or lengths', () => {
expect(validateIE('0918614000103', 'df')).toBe(false); // prefix not 07/08
expect(validateIE('1234567', 'ba')).toBe(false); // wrong length
expect(validateIE('04035910938', 'to')).toBe(false); // invalid TO prefix
expect(validateIE('12345', 'ap')).toBe(false); // wrong length
});
});
});
5 changes: 5 additions & 0 deletions src/__tests__/validatePhone.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ describe('validatePhone', () => {
expect(validatePhone('11111111111')).toBe(false);
expect(validatePhone('22222222')).toBe(false);
});

it('should return false for non-string inputs instead of throwing', () => {
expect(validatePhone(null as unknown as string)).toBe(false);
expect(validatePhone(undefined as unknown as string)).toBe(false);
});
});
10 changes: 4 additions & 6 deletions src/validations/ie/states/sp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ function calcFirstDigit(body: string): number {
for (let i = 0; i < body.length; i++) {
sum += Number.parseInt(body.charAt(i), 10) * weight[i];
}
const dig = sum % 11;
const digit = dig.toString();
return Number.parseInt(digit.substring(digit.length - 1), 10);
// sum % 11 is 0-10; the SP rule uses its last digit (10 -> 0).
return (sum % 11) % 10;
}

/**
Expand All @@ -33,9 +32,8 @@ function calcSecondDigit(body: string): number {
weight = 10;
}
}
const dig = sum % 11;
const digit = dig.toString();
return Number.parseInt(digit.substring(digit.length - 1), 10);
// sum % 11 is 0-10; the SP rule uses its last digit (10 -> 0).
return (sum % 11) % 10;
}

/**
Expand Down
10 changes: 7 additions & 3 deletions src/validations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
* characters.
* @returns a boolean value.
*/
export const isRepeated = (ref: string) => {
const ret = ref.replace(new RegExp(ref[0], 'g'), '').trim().length === 0;
return ret;
export const isRepeated = (ref: string): boolean => {
for (let i = 1; i < ref.length; i++) {
if (ref[i] !== ref[0]) {
return false;
}
}
return true;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion src/validations/validateCNH.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isRepeated } from './utils';
* @returns The function `validateCNH` returns a boolean value, indicating whether the provided CNH is
* valid or not.
*/
export function validateCNH(value: string) {
export function validateCNH(value: string): boolean {
const cnh = String(value).replace(/\D/g, '');

if (cnh.length !== 11 || isRepeated(cnh)) {
Expand Down
7 changes: 4 additions & 3 deletions src/validations/validateCNPJ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ function charToNumber(char: string): number {
* @returns The calculated check digit (0-9).
*/
function generateChecksum(base: string, weights: number[]): number {
const sum = base
.split('')
.reduce((acc, char, i) => acc + charToNumber(char) * weights[i], 0);
let sum = 0;
for (let i = 0; i < base.length; i++) {
sum += charToNumber(base[i]) * weights[i];
}
const remainder = sum % 11;

return remainder < 2 ? 0 : 11 - remainder;
Expand Down
2 changes: 1 addition & 1 deletion src/validations/validateCPF.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function validateCPF(value: string): boolean {
return false;
}
const valWithoutDvs = clearValue.substring(0, clearValue.length - 2);
if (!clearValue || isRepeated(clearValue)) {
if (isRepeated(clearValue)) {
return false;
}
const dv1 = mod11(valWithoutDvs, 12);
Expand Down
2 changes: 1 addition & 1 deletion src/validations/validateEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* valid, and `false` otherwise.
*/
const emailRegex =
/^[\w!#$%&'*+/=?`{|}~^-]+(?:\.[\w!#$%&'*+/=?`{|}~^-]+)*@(?:[A-Z0-9-]+\.)+[A-Z]{2,10}$/i;
/^[\w!#$%&'*+/=?`{|}~^-]+(?:\.[\w!#$%&'*+/=?`{|}~^-]+)*@(?:[A-Z0-9-]+\.)+[A-Z]{2,24}$/i;

export function validateEmail(value: string): boolean {
return emailRegex.test(value);
Expand Down
15 changes: 6 additions & 9 deletions src/validations/validatePIS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import { isRepeated } from './utils';
function generateChecksum(base: string | number, weight: number[]): number {
const digits = String(base).replace(/[^\d]/g, '');

return digits
.split('')
.reduce((acc, digit, i) => acc + +digit * weight[i], 0);
let sum = 0;
for (let i = 0; i < digits.length; i++) {
sum += +digits[i] * weight[i];
}
return sum;
}

const weights = [3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
const onlyDigitsRegex = /^[0-9]+$/;

/**
* The function `validatePIS` validates a PIS number, which is a Brazilian social identification
Expand All @@ -31,11 +32,7 @@ const onlyDigitsRegex = /^[0-9]+$/;
export function validatePIS(pis: string): boolean {
const pisStr = String(pis).replace(/\D/g, '');

if (
pisStr.length !== 11 ||
isRepeated(pisStr) ||
!onlyDigitsRegex.test(pisStr)
) {
if (pisStr.length !== 11 || isRepeated(pisStr)) {
return false;
}

Expand Down
Loading