Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
20 changes: 19 additions & 1 deletion src/utilities/cnpj/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ describe('format', () => {
expect(format('46843485000186')).toBe('46.843.485/0001-86');
});

test('should format cnpj alphanumeric with mask', () => {
expect(format('')).toBe('');
expect(format('Q')).toBe('Q');
expect(format('Q0')).toBe('Q0');
expect(format('Q0S')).toBe('Q0.S');
expect(format('Q0SL')).toBe('Q0.SL');
expect(format('Q0SLF')).toBe('Q0.SLF');
expect(format('Q0SLFM')).toBe('Q0.SLF.M');
expect(format('Q0SLFMB')).toBe('Q0.SLF.MB');
expect(format('Q0SLFMBD')).toBe('Q0.SLF.MBD');
expect(format('Q0SLFMBD7')).toBe('Q0.SLF.MBD/7');
expect(format('Q0SLFMBD7V')).toBe('Q0.SLF.MBD/7V');
expect(format('Q0SLFMBD7VX')).toBe('Q0.SLF.MBD/7VX');
expect(format('Q0SLFMBD7VX4')).toBe('Q0.SLF.MBD/7VX4');
expect(format('Q0SLFMBD7VX43')).toBe('Q0.SLF.MBD/7VX4-3');
expect(format('q0SLFMBD7VX439')).toBe('Q0.SLF.MBD/7VX4-39');
});

test('should format number cnpj with mask', () => {
expect(format(4)).toBe('4');
expect(format(46)).toBe('46');
Expand Down Expand Up @@ -76,7 +94,7 @@ describe('format', () => {
});

test('should remove all non numeric characters', () => {
expect(format('46.?ABC843.485/0001-86abc')).toBe('46.843.485/0001-86');
expect(format('46.?ABC843.485/0001-86abc')).toBe('46.ABC.843/4850-00');
});
});

Expand Down
53 changes: 43 additions & 10 deletions src/utilities/cnpj/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isLastChar, onlyNumbers, generateChecksum, generateRandomNumber } from '../../helpers';
import { isLastChar } from '../../helpers';

export const LENGTH = 14;

Expand Down Expand Up @@ -27,12 +27,15 @@ export const FIRST_CHECK_DIGIT_WEIGHTS = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];

export const SECOND_CHECK_DIGIT_WEIGHTS = [6, ...FIRST_CHECK_DIGIT_WEIGHTS];

const VALID_CHARS_CNPJ = '0123456789ABCDFGHIJKLMNPQRSVWXYZ';

export interface FormatCnpjOptions {
pad?: boolean;
}

export function format(cnpj: string | number, options: FormatCnpjOptions = {}): string {
let digits = onlyNumbers(cnpj);
const valueCNPJUppercased = String(cnpj).toUpperCase();
let digits = onlyValidCNPJAlphanumeric(valueCNPJUppercased);

if (options.pad) {
digits = digits.padStart(LENGTH, '0');
Expand All @@ -54,20 +57,50 @@ export function format(cnpj: string | number, options: FormatCnpjOptions = {}):
}, '');
}

function charToAsciiValue(char: string): number {
return char.charCodeAt(0) - 48;
}

function onlyValidCNPJAlphanumeric(input: string): string {
return input
.split('')
.filter((char) => VALID_CHARS_CNPJ.includes(char))
.join('');
}

function generateChecksumCNPJ(base: string, weight: number[]): number {
const digits = onlyValidCNPJAlphanumeric(base);

return digits.split('').reduce((acc, char, i) => {
const value = charToAsciiValue(char);
return acc + value * weight[i];
}, 0);
}

function generateCNPJAlphanumericChars(length: number): string {
const charset = VALID_CHARS_CNPJ;
return Array(length)
.fill('')
.map(() => charset[Math.floor(Math.random() * charset.length)])
.join('');
}

export function generate(): string {
const baseCNPJ = generateRandomNumber(LENGTH - 2);
const baseCNPJ = generateCNPJAlphanumericChars(LENGTH - 2);

const firstCheckDigitMod = generateChecksum(baseCNPJ, FIRST_CHECK_DIGIT_WEIGHTS) % 11;
const firstCheckDigitMod = generateChecksumCNPJ(baseCNPJ, FIRST_CHECK_DIGIT_WEIGHTS) % 11;
const firstCheckDigit = (firstCheckDigitMod < 2 ? 0 : 11 - firstCheckDigitMod).toString();

const secondCheckDigitMod = generateChecksum(baseCNPJ + firstCheckDigit, SECOND_CHECK_DIGIT_WEIGHTS) % 11;
const secondCheckDigitMod = generateChecksumCNPJ(baseCNPJ + firstCheckDigit, SECOND_CHECK_DIGIT_WEIGHTS) % 11;
const secondCheckDigit = (secondCheckDigitMod < 2 ? 0 : 11 - secondCheckDigitMod).toString();

return `${baseCNPJ}${firstCheckDigit}${secondCheckDigit}`;
}

export function isValidFormat(cnpj: string): boolean {
return /^\d{2}\.?\d{3}\.?\d{3}\/?\d{4}-?\d{2}$/.test(cnpj);
function isValidFormat(cnpj: string): boolean {
return /^[0-9ABCDFGHIJKLMNPQRSVWXYZ]{2}\.?[0-9ABCDFGHIJKLMNPQRSVWXYZ]{3}\.?[0-9ABCDFGHIJKLMNPQRSVWXYZ]{3}\/?[0-9ABCDFGHIJKLMNPQRSVWXYZ]{4}-?\d{2}$/.test(
cnpj
);
}

export function isReservedNumber(cpf: string): boolean {
Expand All @@ -84,7 +117,7 @@ export function isValidChecksum(cnpj: string): boolean {
}

const mod =
generateChecksum(
generateChecksumCNPJ(
cnpj
.slice(0, i)
.split('')
Expand All @@ -99,7 +132,7 @@ export function isValidChecksum(cnpj: string): boolean {
export function isValid(cnpj: string): boolean {
if (!cnpj || typeof cnpj !== 'string') return false;

const numbers = onlyNumbers(cnpj);
const validValue = onlyValidCNPJAlphanumeric(cnpj);

return isValidFormat(cnpj) && !isReservedNumber(numbers) && isValidChecksum(numbers);
return isValidFormat(cnpj) && !isReservedNumber(validValue) && isValidChecksum(validValue);
}
3 changes: 2 additions & 1 deletion src/utilities/email/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ const MAX_RECIPIENT_LENGTH = 64;
const MAX_DOMAIN_LENGTH = 253;
const MAX_EMAIL_LENGTH = MAX_RECIPIENT_LENGTH + 1 + MAX_DOMAIN_LENGTH;

const validEmailRegex = /^([!#$%&'*+\-/=?^_`{|}~]{0,1}([a-zA-Z0-9][!#$%&'*+\-/=?^_`{|}~.]{0,1})+)@(([a-zA-Z0-9][-.]{0,1})+)([.]{1}[a-zA-Z0-9]+)$/;
const validEmailRegex =
/^([!#$%&'*+\-/=?^_`{|}~]{0,1}([a-zA-Z0-9][!#$%&'*+\-/=?^_`{|}~.]{0,1})+)@(([a-zA-Z0-9][-.]{0,1})+)([.]{1}[a-zA-Z0-9]+)$/;

const stringIsBiggerThan = (len: number, ...strs: string[]): boolean =>
strs.reduce((length, s) => length + s.length, 0) > len;
Expand Down