Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refatoração #13

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
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
9 changes: 0 additions & 9 deletions .editorconfig

This file was deleted.

4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
node_modules/
dist/

*-*.tgz
dist/
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015-2019 Matheus Alves
Copyright (c) 2015-2021 Matheus Alves

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
10 changes: 6 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module.exports.format = require('./src/format')
import { format, isValid, generate } from './lib/cpf'

module.exports.generate = require('./src/generate')

module.exports.isValid = require('./src/is-valid')
export default {
format,
isValid,
generate
}
8 changes: 0 additions & 8 deletions jasmine.json

This file was deleted.

151 changes: 151 additions & 0 deletions lib/cpf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* Format a CPF number.
*
* @param {string} cpf
* @returns {string}
* @api public
*/
export function format(cpf) {
if (typeof cpf !== 'string') throw new TypeError('Must be a string')
const digits = cpf.replace(/\D/g, '')

// Must have 11 digits
if (digits.length !== 11) throw new Error('Invalid CPF number')

const regex = /^(\d{3})(\d{3})(\d{3})(\d{2})$/
const mask = '$1.$2.$3-$4'
return digits.replace(regex, mask)
}

/**
* Check if a CPF number is valid.
*
* Options:
* - byLength -- do not compare the check digits
*
* @param {string} cpf
* @param {object} [options]
* @returns {boolean}
* @api public
*/
export function isValid(cpf, options = {
byLength: false
}) {
if (typeof cpf !== 'string') throw new TypeError('Must be a string')
const digits = Array
.from(cpf.replace(/\D/g, ''))
.map(Number)

// Must have 11 digits
if (digits.length !== 11) return false
// Check if all digits are the same (ignore CPFs like '111.111.111-11')
if (areIdentical(digits)) return false
// Check if has 11 digits
if (options.byLength) return true

const baseDigits = digits.slice(0, 9)
const checkDigits = digits.slice(9)
return areEqual(checkDigits, calcCheckDigits(baseDigits))
}

/**
* Generate a random CPF number.
*
* Options:
* - valid -- generate a valid CPF number
* - formatted -- generate a formatted CPF number
*
* @param {object} [options]
* @returns {string}
* @api public
*/
export function generate(options = {
valid: true,
formatted: true,
}) {
const { valid, formatted } = options
const digits = randomInts(9, 9)
const checkDigits = valid ? calcCheckDigits(digits) : randomInts(2, 9)
const cpf = [...digits, ...checkDigits].join('')
return formatted ? format(cpf) : cpf
}

/**
* Calculate the check digits of a CPF number.
*
* @param {number[]} digits
* @returns {number[]}
* @api private
*/
export function calcCheckDigits(digits) {
// Algorithm found in Wikipedia <https://w.wiki/zcv> (pt-BR)
if (!Array.isArray(digits)) throw new TypeError('Must be an array')
if (digits.length !== 9) throw new Error('Must have 9 digits')
const checkDigits = [0, 0]
digits.reverse()
for (let index = 0; index < digits.length; index++) {
checkDigits[0] += digits[index] * (9 - (index % 10))
checkDigits[1] += digits[index] * (9 - ((index + 1) % 10))
}
checkDigits[0] = (checkDigits[0] % 11) % 10
checkDigits[1] = ((checkDigits[1] + checkDigits[0] * 9) % 11) % 10
return checkDigits
}

/**
* Check if two values are equal.
*
* @param {any} a
* @param {any} b
* @return {boolean}
* @api private
*/
export function areEqual(a, b) {
const strA = JSON.stringify(a)
const strB = JSON.stringify(b)
return strA === strB
}

/**
* Check if all items in an array are identical.
*
* @param {Array} array
* @return {boolean}
* @api private
*/
export function areIdentical(array = []) {
if (!Array.isArray(array)) throw new TypeError('Must be an array')
return array.every((value, index, self) => {
// Ignore the first item
if (index === 0) return true
// The current item must be equals the previous
const current = JSON.stringify(value)
const previous = JSON.stringify(self[index - 1])
return current === previous
})
}

/**
* Generate a random number.
*
* @param {number} max
* @return {number}
* @api private
*/
export function random(max = 0) {
return Math.floor(Math.random() * max + 1)
}

/**
* Generate a array with random digits.
*
* @param {number} length
* @param {number} max
* @return {number}
* @api private
*/
export function randomInts(length = 1, max = 1) {
return Array
.from(Array(length)) // Create
.map(() => random(max)) // Fill
}
72 changes: 72 additions & 0 deletions lib/cpf.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import test from 'ava'
import {
calcCheckDigits,
areEqual,
areIdentical,
random,
randomInts,
format,
isValid,
generate
} from './cpf'

test('Should calculate the check digits', t => {
t.deepEqual(calcCheckDigits([1, 1, 1, 4, 4, 4, 7, 7, 7]), [3, 5])
})

test('Should check if two values are equal', t => {
t.true(areEqual([4, 2], [4, 2]))
t.false(areEqual([4, 2], [2, 4]))
})

test('Should check if all items in an array are identical', t => {
t.true(areIdentical([1, 1, 1]))
t.false(areIdentical([1, 2, 3]))
})

test('Should generate a array with random numbers', t => {
t.true(randomInts(10, 10).length === 10)
t.true(randomInts(10, 10).every(i => typeof i === 'number'))
t.true(randomInts(10, 10).every(i => i <= 10))
})

test('Should generate a random number', t => {
t.true(random(10) <= 10)
t.true(random(20) <= 20)
t.true(random(30) <= 30)
})

test('Should format CPF numbers', t => {
t.is(format('11144477735'), '111.444.777-35')
t.is(format('111.44477735'), '111.444.777-35')
t.is(format('111.444.77735'), '111.444.777-35')
t.is(format('111.444.777-35'), '111.444.777-35')
})

test('Should generate formatted CPF numbers', t => {
t.true(/^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(generate()))
t.true(/^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(generate()))
t.true(/^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(generate()))
})

test('Should generate unformatted CPF numbers', t => {
t.true(/^\d{11}$/.test(generate({ formatted: false })))
t.true(/^\d{11}$/.test(generate({ formatted: false })))
t.true(/^\d{11}$/.test(generate({ formatted: false })))
})

test('Should validate CPF numbers', t => {
t.true(isValid('111.444.777-35'))
t.false(isValid('111.444.777-42'))
})

test('Should validate CPF numbers by length', t => {
t.true(isValid('111.444.777-35', { byLength: true }))
t.true(isValid('111.444.777-42', { byLength: true }))
})

test('Should ignore when all digits are identical', t => {
t.false(isValid('111.111.111-11'))
t.false(isValid('444.444.444-44'))
t.false(isValid('777.777.777-77'))
})
Loading