Skip to content

Commit 58c345f

Browse files
authored
feat: strengthen validator correctness and repo hygiene (#77)
* feat: strengthen validator correctness and repo hygiene * Address bot review feedback
1 parent b776c5f commit 58c345f

49 files changed

Lines changed: 1120 additions & 442 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,26 @@ jobs:
7272
- run: bun install --frozen-lockfile
7373
- run: bun test
7474

75+
build:
76+
name: Build
77+
if: >-
78+
always()
79+
&& (github.event_name != 'pull_request'
80+
|| needs.trust-check.outputs.trusted == 'true')
81+
needs: [trust-check]
82+
runs-on: ubuntu-latest
83+
steps:
84+
- uses: actions/checkout@v6
85+
- uses: oven-sh/setup-bun@v2
86+
- run: bun install --frozen-lockfile
87+
- run: bun run typecheck
88+
- run: bun run build
89+
- run: bun run sync-exports:check
90+
7591
publish:
7692
name: Publish
7793
if: startsWith(github.ref, 'refs/tags/v')
78-
needs: [test, lint]
94+
needs: [test, lint, build]
7995
runs-on: ubuntu-latest
8096
permissions:
8197
contents: write

.oxlintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"no-shadow": "error",
66
"require-await": "error",
77
"no-useless-catch": "error",
8-
"no-non-null-assertion": "error",
8+
"no-non-null-assertion": "off",
99
"typescript/no-explicit-any": "error",
1010
"typescript/no-dynamic-delete": "error",
1111
"typescript/consistent-type-definitions": [

CONTRIBUTING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ a pull request.
1515
```bash
1616
# Prerequisites: Bun
1717
bun install
18+
bun run typecheck # type-check source
1819
bun test # run tests
1920
bun run lint # oxlint
2021
bun run format # oxfmt
@@ -25,7 +26,7 @@ bun run format # oxfmt
2526
1. Create `src/{cc}/{id}.ts` implementing the
2627
`Validator` type from `src/types.ts`.
2728
2. Export from `src/{cc}/mod.ts`.
28-
3. Add an entry point in `package.json` exports.
29+
3. Run `bun run sync-exports`.
2930
4. Write tests in `__test__/{cc}.test.ts`.
3031
5. Update `src/types.ts` if the country is new
3132
(add to `CountryCode` union).
@@ -41,7 +42,7 @@ Each validator must export:
4142

4243
- One logical change per PR.
4344
- Include tests for all validators.
44-
- Run `bun test && bun run lint && bun run format`
45+
- Run `bun run typecheck && bun test && bun run lint && bun run format`
4546
before submitting.
4647
- Use [Conventional Commits](https://www.conventionalcommits.org/):
4748
`feat:`, `fix:`, `chore:`, `docs:`.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,8 @@ handled automatically.
313313

314314
```bash
315315
bun install
316-
bun test # 4,185 tests
316+
bun run typecheck
317+
bun test
317318
bun run lint # oxlint
318319
bun run format # oxfmt
319320
```

__test__/cz.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ describe("cz.rc", () => {
7070
expect(r.valid).toBe(true);
7171
});
7272

73+
test("rejects 9-digit numbers using modern +20 month overflow", () => {
74+
const r = cz.rc.validate("913216323");
75+
expect(r.valid).toBe(false);
76+
if (!r.valid) {
77+
expect(r.error.code).toBe("INVALID_COMPONENT");
78+
}
79+
});
80+
7381
test("invalid component (bad date)", () => {
7482
const r = cz.rc.validate("1103492745");
7583
expect(r.valid).toBe(false);

__test__/ee.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ describe("ee.ik", () => {
6161
}
6262
});
6363

64+
test("rejects 7/8 century digits not used by the official format", () => {
65+
const r = ee.ik.validate("76805280109");
66+
expect(r.valid).toBe(false);
67+
if (!r.valid) {
68+
expect(r.error.code).toBe("INVALID_COMPONENT");
69+
}
70+
});
71+
6472
test("metadata", () => {
6573
expect(ee.ik.country).toBe("EE");
6674
expect(ee.ik.entityType).toBe("person");

__test__/lv.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ describe("lv.vat", () => {
1313
expect(r.valid).toBe(true);
1414
});
1515

16+
test("valid Latvian new-format personal VAT", () => {
17+
const r = lv.vat.validate("LV32999999999");
18+
expect(r.valid).toBe(true);
19+
});
20+
1621
test("invalid checksum (legal)", () => {
1722
const r = lv.vat.validate("LV40003521601");
1823
expect(r.valid).toBe(false);
@@ -29,6 +34,22 @@ describe("lv.vat", () => {
2934
}
3035
});
3136

37+
test("new-format personal VAT must use second digit 2-9", () => {
38+
const r = lv.vat.validate("LV31000000000");
39+
expect(r.valid).toBe(false);
40+
if (!r.valid) {
41+
expect(r.error.code).toBe("INVALID_COMPONENT");
42+
}
43+
});
44+
45+
test("old personal VAT must use century digit 0, 1 or 2", () => {
46+
const r = lv.vat.validate("LV28190736398");
47+
expect(r.valid).toBe(false);
48+
if (!r.valid) {
49+
expect(r.error.code).toBe("INVALID_COMPONENT");
50+
}
51+
});
52+
3253
test("metadata", () => {
3354
expect(lv.vat.country).toBe("LV");
3455
});

__test__/nl.test.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ import { describe, expect, test } from "bun:test";
33
import { nl } from "../src";
44

55
describe("nl.vat", () => {
6-
test("valid Dutch VAT", () => {
7-
const r = nl.vat.validate("NL123456789B13");
6+
test("valid Dutch VAT ID from official example", () => {
7+
const r = nl.vat.validate("NL000099998B57");
88
expect(r.valid).toBe(true);
99
});
1010

11-
test("invalid checksum", () => {
12-
const r = nl.vat.validate("NL123456789B14");
13-
expect(r.valid).toBe(false);
14-
if (!r.valid) {
15-
expect(r.error.code).toBe("INVALID_CHECKSUM");
16-
}
11+
test("accepts leading zeros in the 9-digit section", () => {
12+
const r = nl.vat.validate("NL001234567B01");
13+
expect(r.valid).toBe(true);
1714
});
1815

1916
test("missing B", () => {
@@ -32,6 +29,14 @@ describe("nl.vat", () => {
3229
}
3330
});
3431

32+
test("rejects non-digit suffix", () => {
33+
const r = nl.vat.validate("NL000099998BA7");
34+
expect(r.valid).toBe(false);
35+
if (!r.valid) {
36+
expect(r.error.code).toBe("INVALID_FORMAT");
37+
}
38+
});
39+
3540
test("metadata", () => {
3641
expect(nl.vat.country).toBe("NL");
3742
});

0 commit comments

Comments
 (0)