Skip to content

Commit 09e473e

Browse files
authored
strict null checks (#19)
1 parent f27dc31 commit 09e473e

27 files changed

+401
-12
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.9.0] - 2025-09-07
11+
12+
### Added
13+
14+
- **Comprehensive Null/Undefined Safety** - All functions now handle null/undefined gracefully
15+
- Added defensive null checks to 22+ functions that previously lacked them
16+
- Consistent behavior across all utilities: string functions preserve null/undefined, boolean functions return false, array functions return empty arrays
17+
- Zero runtime errors - functions no longer throw TypeErrors on null/undefined inputs
18+
- Maintains full backward compatibility for valid string inputs
19+
- Added 36 comprehensive tests for null/undefined handling
20+
- No performance impact - benchmarks show consistent speed
21+
- Minimal bundle size impact - only 0.02 kB increase
22+
23+
### Improved
24+
25+
- **Developer Experience** - Safer API with predictable edge case handling
26+
- Users no longer need defensive checks before calling functions
27+
- All functions behave consistently with falsy values
28+
- Better alignment with TypeScript's strict null checks
29+
1030
## [0.8.0] - 2025-09-07
1131

1232
### Added

README.md

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Ultra-lightweight string utilities with zero dependencies. Tree-shakeable, fully
1919
-**ESM & CJS** - Works everywhere
2020
- 🧪 **100% tested** - Reliable and production-ready
2121
- 🔒 **Type-safe** - Written in strict TypeScript with enhanced type inference and compile-time transformations
22+
- 🛡️ **Null-safe** - All functions handle null/undefined gracefully without throwing errors
2223
- 📝 **Well documented** - JSDoc comments for all functions
2324

2425
## Installation
@@ -901,15 +902,15 @@ const result = camelCase(userInput);
901902
#### All Case Conversions Support Template Literals
902903

903904
```typescript
904-
camelCase("hello-world"); // Type: "helloWorld"
905-
kebabCase("helloWorld"); // Type: "hello-world"
906-
snakeCase("HelloWorld"); // Type: "hello_world"
907-
pascalCase("hello-world"); // Type: "HelloWorld"
908-
constantCase("helloWorld"); // Type: "HELLO_WORLD"
909-
dotCase("HelloWorld"); // Type: "hello.world"
910-
pathCase("helloWorld"); // Type: "hello/world"
905+
camelCase("hello-world"); // Type: "helloWorld"
906+
kebabCase("helloWorld"); // Type: "hello-world"
907+
snakeCase("HelloWorld"); // Type: "hello_world"
908+
pascalCase("hello-world"); // Type: "HelloWorld"
909+
constantCase("helloWorld"); // Type: "HELLO_WORLD"
910+
dotCase("HelloWorld"); // Type: "hello.world"
911+
pathCase("helloWorld"); // Type: "hello/world"
911912
sentenceCase("hello-world"); // Type: "Hello world"
912-
titleCase("hello-world"); // Type: "Hello World"
913+
titleCase("hello-world"); // Type: "Hello World"
913914
```
914915

915916
#### Type-Safe Configuration Objects
@@ -943,11 +944,39 @@ type MethodNames = {
943944
```
944945

945946
Benefits:
947+
946948
-**Zero runtime cost** - All transformations happen at compile time
947949
-**Better IDE support** - Autocomplete shows exact transformed strings
948950
-**Type safety** - Catch typos and incorrect transformations during development
949951
-**Backward compatible** - Runtime strings work exactly as before
950952

953+
### Null/Undefined Safety
954+
955+
All functions in nano-string-utils handle null and undefined inputs gracefully:
956+
957+
```typescript
958+
// No more runtime errors!
959+
slugify(null); // Returns: null
960+
slugify(undefined); // Returns: undefined
961+
slugify(""); // Returns: ""
962+
963+
// Consistent behavior across all functions
964+
isEmail(null); // Returns: false (validation functions)
965+
words(null); // Returns: [] (array functions)
966+
wordCount(null); // Returns: 0 (counting functions)
967+
968+
// Safe to use without defensive checks
969+
const userInput = getUserInput(); // might be null/undefined
970+
const slug = slugify(userInput); // Won't throw!
971+
```
972+
973+
This means:
974+
975+
-**No TypeErrors** - Functions never throw on null/undefined
976+
-**Predictable behavior** - Consistent handling across all utilities
977+
-**Cleaner code** - No need for defensive checks before calling functions
978+
-**Zero performance cost** - Minimal overhead from null checks
979+
951980
## Bundle Size
952981

953982
Each utility is optimized to be as small as possible:

jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zheruel/nano-string-utils",
3-
"version": "0.8.0",
3+
"version": "0.9.0",
44
"exports": "./src/index.ts",
55
"publish": {
66
"include": ["src/**/*.ts", "README.md", "LICENSE", "CHANGELOG.md"],

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nano-string-utils",
3-
"version": "0.8.0",
3+
"version": "0.9.0",
44
"description": "Ultra-lightweight string utilities with zero dependencies",
55
"type": "module",
66
"main": "./dist/index.cjs",

src/camelCase.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import type { CamelCase } from "./types/string-literals.js";
1616
export function camelCase<T extends string>(str: T): CamelCase<T>;
1717
export function camelCase(str: string): string;
1818
export function camelCase(str: string): string {
19+
if (!str) return str;
20+
1921
const wordList = words(str);
2022

2123
if (wordList.length === 0) return "";

src/codePoints.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* codePoints('a👍b') // [97, 128077, 98]
1010
*/
1111
export function codePoints(str: string): number[] {
12+
if (!str) return [];
13+
1214
const points: number[] = [];
1315
for (const char of str) {
1416
const point = char.codePointAt(0);

src/deburr.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const SPECIAL_CHARS_MAP: Record<string, string> = {
2929
* deburr('São Paulo') // 'Sao Paulo'
3030
*/
3131
export function deburr(str: string): string {
32+
if (!str) return str;
33+
3234
// Replace special characters with single regex pass
3335
const result = str.replace(
3436
SPECIAL_CHARS_PATTERN,

src/escapeHtml.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const HTML_ENTITIES: Record<string, string> = {
1717
* escapeHtml("It's <script>") // 'It&#39;s &lt;script&gt;'
1818
*/
1919
export const escapeHtml = (str: string): string => {
20+
if (!str) return str;
2021
return str.replace(
2122
HTML_ESCAPE_REGEX,
2223
(match) => HTML_ENTITIES[match] ?? match

src/hashString.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* hashString('world') // 3582672807
88
*/
99
export const hashString = (str: string): number => {
10+
if (str == null) return 0;
11+
1012
let hash = 2166136261; // FNV offset basis
1113

1214
if (str.length === 0) return hash >>> 0;

src/isASCII.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* isASCII('') // true
1111
*/
1212
export function isASCII(str: string): boolean {
13+
if (str == null) return false;
14+
1315
for (let i = 0; i < str.length; i++) {
1416
if (str.charCodeAt(i) > 127) {
1517
return false;

0 commit comments

Comments
 (0)