Skip to content

Commit b42dfe2

Browse files
authored
Merge pull request #1 from webdeveric/dev
Added `luhn()`
2 parents 5f133fb + 9be49d1 commit b42dfe2

20 files changed

+254
-41
lines changed

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"import/no-extraneous-dependencies": [
3232
"error",
3333
{
34-
"devDependencies": ["./vitest.config.mts", "./lint-staged.config.mjs", "./test/**/*"]
34+
"devDependencies": ["./vitest.config.mts", "./lint-staged.config.mjs", "./test/**/*", "./bench/**/*"]
3535
}
3636
],
3737
"import/no-relative-packages": "error",
File renamed without changes.

bench/booleanFlagFlip.bench.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
import { bench } from 'vitest';
3+
4+
bench(
5+
'boolean',
6+
() => {
7+
let flag = true;
8+
9+
flag = !flag;
10+
flag = !flag;
11+
},
12+
{
13+
time: 1000,
14+
},
15+
);
16+
17+
bench(
18+
'Bitwise XOR assignment',
19+
() => {
20+
let flag = 1;
21+
22+
flag ^= 1;
23+
flag ^= 1;
24+
},
25+
{
26+
time: 1000,
27+
},
28+
);

bench/luhn-bad-input.bench.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import fastLuhn from 'fast-luhn';
2+
import { validate as luhnValidate } from 'luhn';
3+
import { bench } from 'vitest';
4+
5+
import { luhn } from '../src/luhn.js';
6+
7+
const iterations = 1000;
8+
9+
bench(
10+
'luhn()',
11+
() => {
12+
luhn('bad input');
13+
},
14+
{
15+
iterations,
16+
},
17+
);
18+
19+
bench(
20+
'npm:fast-luhn',
21+
() => {
22+
fastLuhn('bad input');
23+
},
24+
{
25+
iterations,
26+
},
27+
);
28+
29+
bench(
30+
'npm:luhn',
31+
() => {
32+
luhnValidate('bad input');
33+
},
34+
{
35+
iterations,
36+
},
37+
);

bench/luhn.bench.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import fastLuhn from 'fast-luhn';
2+
import { validate as luhnValidate } from 'luhn';
3+
import { bench } from 'vitest';
4+
5+
import { luhn } from '../src/luhn.js';
6+
7+
const iterations = 1000;
8+
9+
bench(
10+
'luhn()',
11+
() => {
12+
luhn('4000000000001000');
13+
},
14+
{
15+
iterations,
16+
},
17+
);
18+
19+
bench(
20+
'npm:fast-luhn',
21+
() => {
22+
fastLuhn('4000000000001000');
23+
},
24+
{
25+
iterations,
26+
},
27+
);
28+
29+
bench(
30+
'npm:luhn',
31+
() => {
32+
luhnValidate('4000000000001000');
33+
},
34+
{
35+
iterations,
36+
},
37+
);

bench/numberParsing.bench.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* eslint-disable @typescript-eslint/no-unused-expressions */
2+
import { bench } from 'vitest';
3+
4+
bench(
5+
'Number.parseInt(x, 10)',
6+
() => {
7+
const input = '123';
8+
9+
Number.parseInt(input[2]!, 10);
10+
},
11+
{
12+
iterations: 10_000,
13+
},
14+
);
15+
16+
bench(
17+
'charCodeAt(x) - 48',
18+
() => {
19+
'123'.charCodeAt(2) - 48;
20+
},
21+
{
22+
iterations: 10_000,
23+
},
24+
);
25+
26+
bench(
27+
'Number(x)',
28+
() => {
29+
Number('123'[2]);
30+
},
31+
{
32+
iterations: 10_000,
33+
},
34+
);

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"CODEOWNERS",
66
"commitlint",
77
"conventionalcommits",
8+
"luhn",
89
"nvmrc",
910
"postbuild",
1011
"tsbuildinfo",

package.json

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
{
2-
"name": "@webdeveric/dual-ts-package-template",
3-
"description": "",
2+
"name": "validation-algorithms",
3+
"description": "Validation algorithms",
44
"version": "0.0.0",
5-
"keywords": [],
5+
"keywords": [
6+
"luhn"
7+
],
68
"author": "Eric King <[email protected]> (https://webdeveric.com/)",
7-
"private": false,
8-
"publishConfig": {
9-
"access": "public"
10-
},
119
"repository": {
1210
"type": "git",
13-
"url": "git+https://github.com/webdeveric/dual-ts-package-template.git"
11+
"url": "git+ssh://git@github.com/webdeveric/validation-algorithms.git"
1412
},
1513
"bugs": {
16-
"url": "https://github.com/webdeveric/dual-ts-package-template/issues"
14+
"url": "https://github.com/webdeveric/validation-algorithms/issues"
1715
},
1816
"license": "MIT",
19-
"packageManager": "[email protected].3+sha512.c0f53ee99477ed969b82b289ad011a5d16bf1623c957e7f29eabe8d0c00b574c29b8c7f54f6c67ee710c73f285c8154d07ce44b46fe2c0eeb476a90441bac371",
17+
"packageManager": "[email protected].4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab",
2018
"sideEffects": false,
2119
"engines": {
2220
"node": ">=20.0.0"
@@ -47,13 +45,15 @@
4745
"validate": "validate-package-exports --check --verify --info",
4846
"postbuild": "echo '{\"type\":\"commonjs\"}' > ./dist/cjs/package.json && echo '{\"type\":\"module\"}' > ./dist/mjs/package.json && pnpm validate",
4947
"typecheck": "tsc --build --verbose",
50-
"lint": "eslint ./*.{js,cjs,mjs,ts,cts,mts} ./src/ ./test/ --ext .ts,.mjs,.cjs",
48+
"lint": "eslint ./*.{js,cjs,mjs,ts,cts,mts} ./src/ ./bench/ ./test/ --ext .ts,.mjs,.cjs",
49+
"bench": "vitest bench",
5150
"test": "vitest",
52-
"coverage": "vitest --coverage",
51+
"coverage": "vitest run --coverage",
5352
"spellcheck": "cspell './{.github,src,test}/**/*.{ts,json}' './*.{js,json,md,mjs,mts}' './package.json' --no-progress",
5453
"prepublishOnly": "pnpm typecheck && pnpm spellcheck && pnpm lint && pnpm coverage && pnpm build",
55-
"format": "prettier --write ./*.{js,json,md,mjs,mts} ./src/ ./test/",
56-
"prepare": "husky"
54+
"format": "prettier --write ./*.{js,json,md,mjs,mts} ./src/ ./bench/ ./test/",
55+
"prepare": "husky",
56+
"dry-release": "semantic-release --dry-run"
5757
},
5858
"prettier": "@webdeveric/prettier-config",
5959
"devDependencies": {
@@ -71,8 +71,10 @@
7171
"eslint-config-prettier": "^9.1.0",
7272
"eslint-import-resolver-typescript": "^3.6.3",
7373
"eslint-plugin-import": "^2.31.0",
74+
"fast-luhn": "^2.0.2",
7475
"husky": "^9.1.7",
7576
"lint-staged": "^15.2.10",
77+
"luhn": "^2.4.1",
7678
"prettier": "^3.4.1",
7779
"rimraf": "^6.0.1",
7880
"semantic-release": "^24.2.0",

pnpm-lock.yaml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readme.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
# `dual-ts-package-template`
1+
# `validation-algorithms`
22

3-
[![Node.js CI](https://github.com/webdeveric/dual-ts-package-template/actions/workflows/node.js.yml/badge.svg)](https://github.com/webdeveric/dual-ts-package-template/actions/workflows/node.js.yml)
3+
[![Node.js CI](https://github.com/webdeveric/validation-algorithms/actions/workflows/node.js.yml/badge.svg)](https://github.com/webdeveric/validation-algorithms/actions/workflows/node.js.yml) [![Release](https://github.com/webdeveric/validation-algorithms/actions/workflows/release.yml/badge.svg)](https://github.com/webdeveric/validation-algorithms/actions/workflows/release.yml)
4+
5+
## Install
6+
7+
`pnpm add validation-algorithms`
8+
9+
`npm i validation-algorithms --saves`
410

511
## Usage
612

7-
Click the "Use this template" button on this page and select "Create a new repository."
13+
```ts
14+
import { luhn } from 'validation-algorithms/luhn';
815

9-
### GitHub CLI
16+
if (luhn('4000000000001000')) {
17+
// passed validation
18+
}
19+
```
1020

11-
Reference: https://cli.github.com/manual/gh_repo_create
21+
## Benchmarks
1222

13-
`gh repo create [YOUR-REPO-NAME] --template webdeveric/dual-ts-package-template --private`
23+
Run `pnpm bench` to run the benchmarks.

src/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
export function demo(): boolean {
2-
return true;
3-
}
1+
export * from './luhn.js';

src/luhn.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export function luhn(input: number | bigint | string): boolean {
2+
const value = typeof input === 'string' ? input : String(input);
3+
4+
// input contained non digit characters.
5+
if (/[^\d]/.test(value)) {
6+
return false;
7+
}
8+
9+
/**
10+
* This is the precomputed result of running this snippet.
11+
*
12+
* ```ts
13+
* '0123456789'.split('').map((digit) => {
14+
* const num = Number.parseInt(digit, 10) * 2;
15+
* return num > 9 ? num - 9 : num;
16+
* });
17+
* ```
18+
*/
19+
const precomputed = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9] as const;
20+
21+
let sum = 0;
22+
23+
for (let index = value.length, isSecond = false; index; isSecond = !isSecond) {
24+
const num = value.charCodeAt(--index) - 48;
25+
26+
sum += isSecond ? precomputed[num]! : num;
27+
}
28+
29+
return sum % 10 === 0;
30+
}

test/index.test.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

test/luhn.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { describe, it, expect } from 'vitest';
2+
3+
import { luhn } from '../src/luhn.js';
4+
5+
describe('luhn()', () => {
6+
it('accepts string', () => {
7+
expect(luhn('4000000000001000')).toBeTruthy();
8+
expect(luhn('4000000000001001')).toBeFalsy();
9+
});
10+
11+
it('accepts bigint', () => {
12+
expect(luhn(4000000000001000n)).toBeTruthy();
13+
expect(luhn(4000000000001001n)).toBeFalsy();
14+
});
15+
16+
it('accepts number', () => {
17+
expect(luhn(4000000000001000)).toBeTruthy();
18+
expect(luhn(4000000000001001)).toBeFalsy();
19+
});
20+
21+
it.each(['not a credit card number', '-1', Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])(
22+
'returns false when giving bad input: %s',
23+
(input) => {
24+
expect(luhn(input)).toBeFalsy();
25+
},
26+
);
27+
});

0 commit comments

Comments
 (0)