# TypeScript rules

Follow these CodeWiki-derived rules when you work in this project.

- Asserting an external value as a complex derived type disguises unvalidated data as something the compiler has proved.
  Source: [Advanced types](https://codewiki.com/typescript/advanced-types/)
- Adding `any` to a type utility or its implementation lets unsafe values bypass conditional branches and property checks, then spreads the problem to callers.
  Source: [Advanced types](https://codewiki.com/typescript/advanced-types/)
- A conditional over a naked type parameter distributes, so its result may answer “how should each union member be handled?” when the requirement asks “does this whole union satisfy the condition?”
  Source: [Advanced types](https://codewiki.com/typescript/advanced-types/)
- Do not treat `?` as “the property's value always includes `undefined`” conflates an absent property with a present property explicitly set to `undefined`.
  Source: [Advanced types](https://codewiki.com/typescript/advanced-types/)
- Do not assume this is safe: generating property names at the type level does not put those properties on a runtime object or guarantee that both sides apply the same case conversion.
  Source: [Advanced types](https://codewiki.com/typescript/advanced-types/)
- Recursively mapping every `object` destroys function call signatures, built-in object semantics, and classes with private state.
  Source: [Advanced types](https://codewiki.com/typescript/advanced-types/)
- Do not assume this is safe: annotating ordinary primitives with the wrapper-object types `String`, `Number`, or `Boolean` introduces methods and assignability rules that do not match everyday JavaScript values.
  Source: [Basic types](https://codewiki.com/typescript/basics/)
- Do not assume this is safe: receiving JSON or third-party data as `any` lets one escape spread through property access, calls, and returns until it fails at runtime.
  Source: [Basic types](https://codewiki.com/typescript/basics/)
- `value as User` and the non-null assertion `value!` only suppress compiler doubt; they neither change nor inspect the runtime value.
  Source: [Basic types](https://codewiki.com/typescript/basics/)
- Using `value || fallback` for nullable data replaces `0`, the empty string, and `false` as well as missing values.
  Source: [Basic types](https://codewiki.com/typescript/basics/)
- An array lookup can produce `undefined` at runtime even when a permissive configuration shows only the element type statically.
  Source: [Basic types](https://codewiki.com/typescript/basics/)
- Do not assume this is safe: exposing a generic brand factory that does not validate.
  Why: `brand(input)` or `input as UserId` can turn any value into a trusted type, making every caller an unaudited construction site.
  Source: [Branded types](https://codewiki.com/typescript/branded-types/)
- Do not treat a brand as runtime validation or authorization evidence.
  Why: A brand neither checks that a database record exists nor proves that the current principal owns it; a branded value from an old cache may also be stale.
  Source: [Branded types](https://codewiki.com/typescript/branded-types/)
- Do not assume operations, normalization, and deserialization preserve a brand.
  Why: Numeric arithmetic and string methods normally return base types; a JSON round trip also produces unproved data, not the original branded value.
  Source: [Branded types](https://codewiki.com/typescript/branded-types/)
- Branding a mutable object as “validated” and then changing it.
  Why: If fields can change after construction, the validated condition can become false while the static brand remains on the variable's type.
  Source: [Branded types](https://codewiki.com/typescript/branded-types/)
- Stacking different brands through the same marker property.
  Why: `Brand, "Integer">` requires one property to have two incompatible string literals and can reduce to `never` instead of representing two completed stages.
  Source: [Branded types](https://codewiki.com/typescript/branded-types/)
- A declaration is more precise than its implementation, so the project looks type-safe while runtime code returns another shape.
  Source: [Declaration files](https://codewiki.com/typescript/declaration-files/)
- A missing module marker puts local helper interfaces into global scope, where they merge unexpectedly with other declarations.
  Source: [Declaration files](https://codewiki.com/typescript/declaration-files/)
- A `declare module "*"` shim or an `any` return type silences diagnostics and disables checking downstream of that dependency.
  Source: [Declaration files](https://codewiki.com/typescript/declaration-files/)
- A green build with `skipLibCheck` is treated as proof that declarations are correct.
  Source: [Declaration files](https://codewiki.com/typescript/declaration-files/)
- A package's type entry and runtime entry use different export shapes or module formats.
  Source: [Declaration files](https://codewiki.com/typescript/declaration-files/)
- When auto-incremented values enter persisted data or a network protocol, inserting or reordering members changes later codes.
  Why: Old data remains a valid number but can now mean a different member.
  Source: [Enums](https://codewiki.com/typescript/enums/)
- `payload.status as OrderStatus` only suppresses the checker.
  Why: It neither confirms that the property exists nor checks that its string belongs to the enum; generated code often sends asserted JSON straight into business branches.
  Source: [Enums](https://codewiki.com/typescript/enums/)
- Calling `Object.keys()` or `Object.values()` directly on a numeric enum returns both directions of the mapping.
  Why: Select options, validation sets, and metric labels can therefore be duplicated or include the wrong runtime type.
  Source: [Enums](https://codewiki.com/typescript/enums/)
- Do not assume this is safe: the string `"APPROVED"` has the same text as `OrderStatus.Approved`, but it is not directly assignable as an `OrderStatus` member.
  Why: Adding an assertion hides an API-design mismatch.
  Source: [Enums](https://codewiki.com/typescript/enums/)
- A consumer can inline values from dependency version A at compile time but load version B at runtime.
  Why: Ambient `const enum` declarations also conflict with some single-file transpilation and `isolatedModules` workflows.
  Source: [Enums](https://codewiki.com/typescript/enums/)
- `(value: any) => any` accepts many types but expresses no relationship between input and output; `any` also spreads unchecked operations to callers.
  Source: [Generics](https://codewiki.com/typescript/generics/)
- Do not assume this is safe: `JSON.parse(text) as T`, `{} as T`, and `value as unknown as T` let an implementation claim any caller-selected result without constructing or validating that value.
  Source: [Generics](https://codewiki.com/typescript/generics/)
- `` checks typed callers only; it doesn't inspect an object from JSON or leave a guard in emitted JavaScript.
  Source: [Generics](https://codewiki.com/typescript/generics/)
- A type parameter used in only one parameter position usually connects nothing and makes a simple signature harder to read.
  Source: [Generics](https://codewiki.com/typescript/generics/)
- Supplying an overly broad type argument can make inconsistent values fit that broad type, losing the most specific return type and useful diagnostics.
  Source: [Generics](https://codewiki.com/typescript/generics/)
- A read-only array utility declared with `T[]` rejects readonly arrays and lets its body mutate data; in `.tsx`, `` on a single-parameter generic arrow may also parse as JSX.
  Source: [Generics](https://codewiki.com/typescript/generics/)
- Do not assume `const` preserves the literal type of object properties.
  Why: `const request = { method: "GET" }` only prevents rebinding `request`; the property stays mutable, so `method` is normally `string`.
  Source: [Literal types](https://codewiki.com/typescript/literal-types/)
- Do not treat `as const` as a runtime freeze or input validator.
  Why: It doesn't call `Object.freeze`, and it can't prove that a string received from JSON or the network belongs to a union.
  Source: [Literal types](https://codewiki.com/typescript/literal-types/)
- Repairing a widening error with a type assertion, such as writing an arbitrary `string` as `value as Status`.
  Why: An assertion asks the checker to trust the author; it neither changes nor inspects the runtime value.
  Source: [Literal types](https://codewiki.com/typescript/literal-types/)
- Adding the broad primitive to a literal union, as in `"auto" | string`.
  Why: Because `string` already includes `"auto"`, the whole union accepts every string and loses its closed-set constraint.
  Source: [Literal types](https://codewiki.com/typescript/literal-types/)
- Do not assume this is safe: returning a generic result from a `default` branch and assuming every union member is safely handled.
  Why: When a new literal member appears, this branch swallows the omission and the compiler has no reason to complain.
  Source: [Literal types](https://codewiki.com/typescript/literal-types/)
- Using number literal unions to represent arbitrary ranges, such as trying to list every valid port or monetary amount.
  Why: A union can enumerate a few named levels, but it can't describe a continuous range or enforce runtime business rules.
  Source: [Literal types](https://codewiki.com/typescript/literal-types/)
- Do not treat `satisfies` as a runtime validator.
  Why: It is removed before JavaScript is emitted, so it cannot inspect requests, JSON, environment variables, or values supplied by JavaScript callers.
  Source: [satisfies operator](https://codewiki.com/typescript/satisfies/)
- Do not assume the target becomes the variable's type.
  Why: If the target has an optional `cache` property and the left-hand object omits it, accessing `object.cache` still fails because the resulting type did not gain a member from nowhere.
  Source: [satisfies operator](https://codewiki.com/typescript/satisfies/)
- Do not assume every literal is preserved.
  Why: A mutable object's `path: "/health"` is still usually inferred as `string` when its target property is `string`; numeric properties similarly widen to `number`.
  Source: [satisfies operator](https://codewiki.com/typescript/satisfies/)
- Checking a program-owned closed set with an open index signature.
  Why: `Record` checks each value that exists but permits every string key, so missing required keys and misspellings can escape the check.
  Source: [satisfies operator](https://codewiki.com/typescript/satisfies/)
- Hiding an error with a broad assertion before adding `satisfies`.
  Why: `value as unknown as Target satisfies Target` checks only an expression already disguised as `Target`; it restores no evidence.
  Source: [satisfies operator](https://codewiki.com/typescript/satisfies/)
- Do not treat a narrower result as a freely mutable contract.
  Why: In TypeScript 6, `{ enabled: true } satisfies { enabled: boolean }` retains `true` for the property, so assigning `false` later fails.
  Source: [satisfies operator](https://codewiki.com/typescript/satisfies/)
- Do not treat `strict` as a runtime validator.
  Why: `JSON.parse(raw) as User`, `response.json() as User`, and arguments from JavaScript receive no structural check from strict mode.
  Source: [Strict mode](https://codewiki.com/typescript/strict-mode/)
- Adding `any`, `as Target`, non-null `!`, or definite-assignment `field!` in bulk to clear the error list.
  Why: These forms suppress the uncertainty the compiler is reporting.
  Source: [Strict mode](https://codewiki.com/typescript/strict-mode/)
- Do not assume `strict: true` includes every strongest check.
  Why: Out-of-bounds indexes, exact write semantics for optional properties, and misspelled overriding methods are not all handled by this umbrella.
  Source: [Strict mode](https://codewiki.com/typescript/strict-mode/)
- Do not assume this is safe: editing `tsconfig.json` while the actual check command does not use it.
  Why: Passing one source file directly to `tsc`, or selecting another project config from the wrong directory, can make a local experiment differ from the build.
  Source: [Strict mode](https://codewiki.com/typescript/strict-mode/)
- Expecting `strictFunctionTypes` to constrain interface method syntax in the same way.
  Why: For compatibility with common class and DOM hierarchies, method parameter checking remains bivariant, so a narrower method can pass through an apparently safe assignment.
  Source: [Strict mode](https://codewiki.com/typescript/strict-mode/)
- Do not treat 100% type coverage as runtime type safety.
  Why: A wrong interface, fabricated declaration file, or `as User` can make the checker trust a fact that does not exist.
  Source: [Type coverage](https://codewiki.com/typescript/type-coverage/)
- Comparing percentages measured under different policies.
  Why: Upgrading TypeScript or `type-coverage`, switching `tsconfig`, or adding generated files changes both numerator and denominator.
  Source: [Type coverage](https://codewiki.com/typescript/type-coverage/)
- Do not assume this is safe: clearing detail output with ignore comments, `ignoreFiles`, or permissive `ignore*` options.
  Why: The score rises without restoring any static evidence.
  Source: [Type coverage](https://codewiki.com/typescript/type-coverage/)
- Do not assume this is safe: replacing `unknown` with `any` to raise coverage, or assuming strict mode penalizes `unknown`.
  Why: The change weakens use-site rules and usually makes propagation wider.
  Source: [Type coverage](https://codewiki.com/typescript/type-coverage/)
- Do not assume this is safe: running only the coverage command without `tsc` and tests.
  Why: An identifier can have a non-`any` type while the code still has an ordinary type error or wrong behavior.
  Source: [Type coverage](https://codewiki.com/typescript/type-coverage/)
- A custom predicate says `value is User`, but its body checks only for a non-null object, allowing arbitrary objects to acquire fields they don't have.
  Source: [Type guards](https://codewiki.com/typescript/type-guards/)
- Do not assume this is safe: a predicate that gives only a sufficient condition instead of a two-way test can also narrow the false branch incorrectly.
  Why: An `isSmallNumber` predicate may claim `value is number` while returning `false` for large numbers.
  Source: [Type guards](https://codewiki.com/typescript/type-guards/)
- Do not assume this is safe: `JSON.parse(text) as User` makes an error disappear without running a runtime check.
  Why: Missing fields, `null`, and wrong primitive types pass into business code unchanged.
  Source: [Type guards](https://codewiki.com/typescript/type-guards/)
- `typeof value === "object"` still includes `null`, while `if (value)` also rejects `""`, `0`, and `false`.
  Why: These broad checks often send valid empty values down the wrong branch.
  Source: [Type guards](https://codewiki.com/typescript/type-guards/)
- `"id" in value` proves only that a property can be found on the object or its prototype chain.
  Why: It doesn't prove that `id` is an own property, a string, or different from `undefined`.
  Source: [Type guards](https://codewiki.com/typescript/type-guards/)
- Do not assume this is safe: `instanceof` depends on a runtime constructor and prototype chain, so deserialized objects, objects from another realm, or instances from a duplicated library may fail.
  Why: Interfaces don't exist at runtime and can't appear on its right-hand side.
  Source: [Type guards](https://codewiki.com/typescript/type-guards/)
- Do not treat inference as if it always chooses the most precise type.
  Why: Mutable bindings, object properties, and array elements normally leave room for future writes, so literals can widen.
  Source: [Type inference](https://codewiki.com/typescript/type-inference/)
- Do not assume an empty array is always `any[]` or always `never[]`.
  Why: Its result depends on context, control flow, compiler options, and declaration position, so an isolated example can't answer for every site.
  Source: [Type inference](https://codewiki.com/typescript/type-inference/)
- Saving a callback in an unannotated variable, then expecting its later use by an array method to fill in its parameter type.
  Why: Contextual typing happens where an expression is checked; it doesn't flow backward and rewrite an independent declaration already checked.
  Source: [Type inference](https://codewiki.com/typescript/type-inference/)
- Deleting a public function's return annotation because the current implementation is “obviously inferred.” A later condition, error sentinel, or optional field can silently widen the exported type and push the break onto callers.
  Source: [Type inference](https://codewiki.com/typescript/type-inference/)
- Taking a concrete-looking type after `JSON.parse(raw)` as successful inference.
  Why: Its standard declaration returns `any`, so frictionless property access means checking has been bypassed, not that the data was proved.
  Source: [Type inference](https://codewiki.com/typescript/type-inference/)
- `if (attempts)` sends `0` down the same false branch as `null`, and `if (name)` drops a valid empty string.
  Why: The type narrows, but the program may violate its domain rules.
  Source: [Type narrowing](https://codewiki.com/typescript/type-narrowing/)
- `function isUser(value): value is User` may check only `"id" in value` while callers believe every required field and field type is valid.
  Why: A predicate is a trusted declaration, not an automatically verified proof.
  Source: [Type narrowing](https://codewiki.com/typescript/type-narrowing/)
- `JSON.parse(raw) as Order` performs no check.
  Why: It suppresses compiler errors while letting a bad shape enter the whole call chain under a precise-looking type.
  Source: [Type narrowing](https://codewiki.com/typescript/type-narrowing/)
- After a property passes a check, assignment to the object or property can change its observed type.
  Why: Callbacks, aliases, and async gaps also make it harder to know whether the runtime value still meets the old condition.
  Source: [Type narrowing](https://codewiki.com/typescript/type-narrowing/)
- A discriminated union's `default: return "unknown"` swallows members added later.
  Why: The code still compiles, but the new state receives no domain-specific handling.
  Source: [Type narrowing](https://codewiki.com/typescript/type-narrowing/)
- When a value is `EmailContact | PhoneContact`, reading `email` or `phone` directly isn't safe.
  Why: The union says that either member may appear; it doesn't promise that every value has every member's fields.
  Source: [Union and intersection types](https://codewiki.com/typescript/union-intersection/)
- Do not assume this is safe: making `data`, `error`, and `progress` all optional permits invalid combinations such as success without data or failure with data.
  Why: A non-null assertion only hides the outcome; it doesn't restore field correlation.
  Source: [Union and intersection types](https://codewiki.com/typescript/union-intersection/)
- `type Combined = A & B` only declares constraints.
  Why: Asserting an object that has only `A` fields to `Combined` creates no `B` fields, so reading them can still produce `undefined`.
  Source: [Union and intersection types](https://codewiki.com/typescript/union-intersection/)
- Do not assume this is safe: `{ id: string } & { id: number }` requires one `id` to satisfy two incompatible types, so the resulting field is `never`.
  Why: An assertion may hide the error briefly, but it spreads an impossible contract to consumers.
  Source: [Union and intersection types](https://codewiki.com/typescript/union-intersection/)
- Returning `"unknown"` directly from the end of a `switch` lets a new union member fall silently into old behavior.
  Why: The code still compiles even though the state machine is incomplete.
  Source: [Union and intersection types](https://codewiki.com/typescript/union-intersection/)
- Do not assume this is safe: `JSON.parse(raw) as PaymentState` skips checks of the container, discriminant, and payload.
  Why: A statically exhaustive `switch` may still receive an arbitrary `status` at runtime.
  Source: [Union and intersection types](https://codewiki.com/typescript/union-intersection/)
- `Partial`, `Required`, and `Readonly` map only the direct properties of `T`.
  Why: Nested objects, arrays, `Date`, `Map`, and functions retain their original type semantics.
  Source: [Utility types](https://codewiki.com/typescript/utility-types/)
- A complete object is assignable to a shape with one requirement omitted, so `return account` can satisfy an `Omit` return type while the runtime object still carries `passwordHash`.
  Source: [Utility types](https://codewiki.com/typescript/utility-types/)
- `Partial` commonly lets callers change IDs, roles, ownership, audit timestamps, and fields that the service should control.
  Why: It also conflates whether a field can be updated with whether that field exists after creation.
  Source: [Utility types](https://codewiki.com/typescript/utility-types/)
- Do not assume this is safe: without `exactOptionalPropertyTypes`, an optional property generally accepts an explicit `undefined`.
  Why: A patch merge then overwrites the old value with `undefined`, which differs from leaving the field absent.
  Source: [Utility types](https://codewiki.com/typescript/utility-types/)
- `Record` statically treats every string lookup as a `V`, but an ordinary runtime object can still lack the key.
  Why: Generated code often calls `handlersname` directly and throws when an unknown name produces `undefined`.
  Source: [Utility types](https://codewiki.com/typescript/utility-types/)
- `Omit` allows `K` to contain property keys outside `keyof T`, so a misspelled exclusion may produce no diagnostic.
  Why: The field remains in the derived type, and a later assertion can hide the result again.
  Source: [Utility types](https://codewiki.com/typescript/utility-types/)
