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.md
source 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
    undefined
    or
    null
    errors at runtime
  • 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

Bad:

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:

  1. You have exhaustively verified the value exists
  2. There's no other way to express it to TypeScript
  3. You document WHY it's safe

Even then, prefer assertion functions over

!
.

Migration Strategy

  1. Search for all uses of
    !
    in codebase
  2. Categorize by pattern (DOM access, array indexing, etc.)
  3. Replace with appropriate type-safe alternative
  4. Test thoroughly after each replacement
  5. 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