Exclamation Does Not Assert Nullability
Unlike Kotlin where object.someValueThatCouldBeNull!!
would assert that someValueThatCouldBeNull
is NOT null (and throw if it is), in TypeScript the object.someValueThatCouldBeNull!
operator tells the compiler:
"Don't worry about this, look away, even if you know this is null, I don't care, let me be".
In other words, even if object.someValueThatCouldBeNull
is in fact NULL, object.someValueThatCouldBeNull!
makes things compile as if it wasn't NULL - provides NO runtime protection.
Hence, instead of !
operator, it's better to use a helper function like:
export function ensure<T>(value: T | null | undefined, name?: string): T {
if (value === undefined) {
throw new Error(`${name ?? 'Value'} is required but was undefined`);
}
if (value === null) {
throw new Error(`${name ?? 'Value'} is required but was null`);
}
return value;
}
GT-Sandbox-Snapshot
Code
import { describe, it, expect, beforeEach, beforeAll } from 'vitest';
describe('Non-null assertion operator (!)', () => {
it('does NOT throw when value is null - crashes at property access instead', () => {
const data: { value: string | null } = {value: null};
// This does NOT throw - the ! operator is compile-time only
const assertedValue = data.value!;
// assertedValue is still null at runtime
expect(assertedValue).toBe(null);
// The error happens when you try to USE the null value
expect(() => {
// This is where it actually crashes
const length = data.value!.length;
}).toThrow('Cannot read properties of null');
});
it('does NOT throw when value is undefined either', () => {
const data: { value?: string } = {};
// Again, this doesn't throw
const assertedValue = data.value!;
// It's still undefined
expect(assertedValue).toBe(undefined);
// Crashes when accessing properties
expect(() => {
const length = data.value!.length;
}).toThrow('Cannot read properties of undefined');
});
it('ensure() helper DOES throw immediately', () => {
function ensure<T>(value: T | null | undefined, name?: string): T {
if (value == null) throw new Error(`${name ?? 'Value'} is required`);
return value;
}
const data: { value: string | null } = {value: null};
// This throws immediately, before any property access
expect(() => {
const safeValue = ensure(data.value, 'data.value');
}).toThrow('data.value is required');
});
});
Command to reproduce:
gt.sandbox.checkout.commit 9f402a4c847a161d5d6a \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./run.sh"
Recorded output of command:
up to date, audited 179 packages in 1s
33 packages are looking for funding
run `npm fund` for details
2 moderate severity vulnerabilities
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
> glassthought-sandbox@1.0.0 test
> vitest run
RUN v0.34.6 /home/nickolaykondratyev/git_repos/glassthought-sandbox
✓ src/main.test.ts (3 tests) 2ms
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 11:56:55
Duration 484ms (transform 34ms, setup 0ms, collect 11ms, tests 2ms, environment 0ms, prepare 68ms)