Claude-skill-registry avoiding-non-null-assertions
Avoid non-null assertion operator (!) and use type-safe alternatives instead
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/avoiding-non-null-assertions" ~/.claude/skills/majiayu000-claude-skill-registry-avoiding-non-null-assertions && rm -rf "$T"
manifest:
skills/data/avoiding-non-null-assertions/SKILL.mdsource content
Avoiding Non-Null Assertions
The non-null assertion operator (
!) is deprecated in modern TypeScript because it bypasses type safety and can lead to runtime errors.
Why Avoid !
!- Bypasses type safety: Tells TypeScript "trust me" without verification
- Runtime errors: Can cause
orundefined
errors at runtimenull - Maintenance burden: Makes refactoring dangerous
- No protection: Removes TypeScript's main benefit
Modern Alternatives
1. Optional Chaining (?.
)
?.Bad:
const userName = user!.profile!.name;
Good:
const userName = user?.profile?.name;
2. Nullish Coalescing (??
)
??Bad:
const value = config!.timeout;
Good:
const value = config?.timeout ?? 5000;
3. Type Guards
Bad:
function processUser(user: User | null) { console.log(user!.name); }
Good:
function processUser(user: User | null) { if (user !== null) { console.log(user.name); } }
4. Early Return Pattern
Bad:
function getUserEmail(userId: number): string { const user = findUser(userId); return user!.email; }
Good:
function getUserEmail(userId: number): string | null { const user = findUser(userId); if (!user) { return null; } return user.email; }
5. Custom Type Guards
Bad:
function handleValue(value: unknown) { return (value as User)!.name; }
Good:
function isUser(value: unknown): value is User { return typeof value === 'object' && value !== null && 'name' in value; } function handleValue(value: unknown) { if (isUser(value)) { return value.name; } throw new Error('Invalid user'); }
6. Narrowing with in
Operator
inBad:
function process(obj: { data?: string }) { console.log(obj.data!.toUpperCase()); }
Good:
function process(obj: { data?: string }) { if ('data' in obj && obj.data !== undefined) { console.log(obj.data.toUpperCase()); } }
7. Array Methods with Type Safety
Bad:
const users: User[] = getUsers(); const firstUser = users[0]!;
Good:
const users: User[] = getUsers(); const firstUser = users.at(0); if (firstUser) { console.log(firstUser.name); }
8. Assertion Functions (TypeScript 3.7+)
Good:
function assertIsDefined<T>(value: T): asserts value is NonNullable<T> { if (value === undefined || value === null) { throw new Error('Value must be defined'); } } function process(value: string | null) { assertIsDefined(value); console.log(value.toUpperCase()); }
DOM Element Access
Bad:
const button = document.getElementById('submit')!; button.addEventListener('click', handler);
Good:
const button = document.getElementById('submit'); if (button) { button.addEventListener('click', handler); }
Or with assertion function:
function assertElement<T extends Element>( element: T | null, selector: string ): asserts element is T { if (!element) { throw new Error(`Element not found: ${selector}`); } } const button = document.getElementById('submit'); assertElement(button, '#submit'); button.addEventListener('click', handler);
When Is !
Acceptable?
!Only in very rare cases where:
- You have exhaustively verified the value exists
- There's no other way to express it to TypeScript
- You document WHY it's safe
Even then, prefer assertion functions over
!.
Migration Strategy
- Search for all uses of
in codebase! - Categorize by pattern (DOM access, array indexing, etc.)
- Replace with appropriate type-safe alternative
- Test thoroughly after each replacement
- Enable linting to prevent future uses
Compiler Configuration
Enable strict checks:
{ "compilerOptions": { "strict": true, "strictNullChecks": true, "noUncheckedIndexedAccess": true } }
Summary
Never use
operator:!
- Use
for optional chaining?. - Use
for default values?? - Use type guards for narrowing
- Use assertion functions when validation is needed
- Let TypeScript protect you from null/undefined errors