typescript-x

内容来源:clawhub · 原始地址 · 查看安装指南

原始内容


name: typescript slug: typescript version: 1.0.5 description: >- Writes, reviews, and debugs type-safe TypeScript: type errors, narrowing, generics, tsconfig strictness, and declaration files. Use when a build hits type errors ("not assignable", "possibly undefined", "excessively deep", "cannot find module"), when eliminating any or unchecked as casts, designing generics or discriminated unions, writing .d.ts files or typing untyped npm packages, configuring tsconfig or module resolution, fixing ESM/CJS import crashes ("ERR_REQUIRE_ESM", "x is not a function"), validating API or JSON data at runtime, migrating JavaScript to TypeScript or upgrading the TypeScript version, or when tsc or the editor gets slow or runs out of memory. homepage: https://clawic.com/skills/typescript changelog: "Full coverage pass: deeper guides, situation-named files, and per-user configuration" metadata: clawdbot: emoji: 🔷 displayName: TypeScript configPaths: - ~/Clawic/data/typescript/


User preferences and memory live in ~/Clawic/data/typescript/ (see setup.md on first use, memory-template.md for the file format). If you have data at an old location (~/typescript/ or ~/clawic/typescript/), move it to ~/Clawic/data/typescript/.

When To Use

  • Decoding type errors: "not assignable" walls, narrowing failures, inference surprises, errors that appear only in CI
  • Designing types for an API surface: generics, discriminated unions, utility types, branded types
  • tsconfig decisions: strict flags, module resolution, target/lib, monorepo project references, JS-to-TS migration order
  • Writing or fixing declaration files (.d.ts, module augmentation, publishing types)
  • Code that compiles green but fails at runtime — ESM/CJS interop crashes, unvalidated external data — or tsc/editor slowness
  • Not for runtime JavaScript semantics (closures, event loop, coercion) — that is the javascript skill

Quick Reference

Situation Play
Value of unknown shape (API, JSON.parse, catch) unknown + narrow before use; never any
Config/lookup object losing literal types satisfies (TS >=4.9), not a type annotation
Union variant not narrowing Add a literal discriminant field + exhaustive never check
40-line "not assignable" error Read the deepest Types of property ... are incompatible line first — root cause is at the bottom; full decoder → errors.md
Cryptic diagnostic (TS2589, TS2742, "no overload matches"), error only in CI errors.md
Generic won't infer / variance confusion / overload design generics.md
Partial/Omit/Record behaving oddly utility-types.md
Untyped npm package, globals, augmentation, publishing types declarations.md
JS conversion, strict-flag rollout, as debt, TS version upgrade migration.md
target/module/paths choices, monorepo, project references tsconfig.md
Green compile, runtime crash (x is not a function, ERR_REQUIRE_ESM) modules.md
External data crossing a trust boundary Validate at runtime at the boundary; static types only downstream → boundaries.md
tsc slow, editor lag, compiler out of memory performance.md
Anything else typed Default: full strict, infer locals, annotate exports

Depth on demand: errors.md diagnostic decoding · generics.md inference, variance, conditional types, overloads · utility-types.md built-in type traps · declarations.md .d.ts, augmentation, publishing · migration.md JS-to-TS, strictness ratchets, TS upgrades · tsconfig.md compiler configuration · modules.md ESM/CJS interop · boundaries.md runtime validation · performance.md compile and editor speed.

Core Rules

  1. Full strict or you are reviewing half the code. Every flag off is a bug class the checker won't report. Greenfield: strict: true day one plus noUncheckedIndexedAccess (TS >=4.1). Legacy: stage flags with a ratchet (migration.md).
  2. unknown in, typed out. Data you didn't construct (network, JSON, env, storage) enters as unknown and gets validated once at the boundary (boundaries.md); past that point no any and no re-checking.
  3. Annotate exports, infer locals. Annotated parameters and return on every exported function put errors at the boundary that caused them, not at 30 call sites downstream; inference inside bodies keeps the noise down.
  4. Make illegal states unrepresentable. n independent optional fields admit 2^n combinations: { data?: T; error?: E } is 4 states of which 2 are illegal. A discriminated union with one variant per legal state encodes exactly the truth.
  5. Every as carries a proof. A cast overrides the checker — attach the runtime guard that makes it true, or a one-line comment saying why it cannot be wrong. An unexplained cast is a deferred bug report.
  6. Exhaustive by construction. Every switch over a union ends in default: x satisfies never (TS >=4.9) — adding a variant then becomes a compile error at every site that must handle it, which is the entire point of the union.
  7. Errors read bottom-up. The first lines of a long diagnostic are wrappers; the root cause is the deepest Types of property ... are incompatible line (errors.md).

Stop Using any

  • any is viral both directions: it silences errors on reads AND poisons everything it's assigned to. unknown only blocks reads until you narrow — same flexibility, none of the spread
  • catch (e) is unknown under strict since TS 4.4 (useUnknownInCatchVariables) — check e instanceof Error before .message
  • Every remaining cast (as) gets either a runtime guard above it or a one-line comment saying why it's safe (rule 5)
  • Ratchet, don't boil the ocean: count anys (typescript-eslint no-explicit-any + no-unsafe-*), record the baseline, fail CI when the count rises

Inference & Widening

Widening:

  • let x = "hello" is string; const x = "hello" is "hello" — mutability drives widening
  • Object and array literals widen their members even under const: const cfg = { mode: "dark" } has mode: stringas const freezes the whole tree (readonly + literals)
  • Literal arguments: primitives infer as literals, objects/arrays widen — <const T> type parameters (TS >=5.0) preserve them at the call site
  • Function return types widen too — annotate the return when a caller needs the literal

Inference limits:

  • Inference flows from arguments to parameters, not through intermediate generics — when a nested generic fails, name the intermediate type and split into two steps
  • No partial type argument inference: fn<T, U> with one explicit argument turns off inference for the rest — use the curried two-call pattern make<T>()(config) to fix one and infer the other
  • NoInfer<T> (TS >=5.4) excludes a position from inference: declare function set<T>(vals: T[], initial: NoInfer<T>): void — without it, a wrong initial widens T instead of erroring
  • Callback parameters get contextual types only when the expected signature is known — assigning the function to a typed variable or parameter is what enables (x) => without annotation

Discriminated Unions & Narrowing

Modeling with unions:

  • Add a literal kind field to each variant — narrowing and exhaustive switches come free (rules 4 and 6)
  • Discriminant must be a literal type — a field typed string discriminates nothing; as const the source values

Narrowing failures:

  • filter(Boolean) doesn't narrow in any TS version — write .filter((x): x is T => Boolean(x)). TS >=5.5 infers predicates from simple lambdas, so .filter(x => x != null) narrows there; below 5.5 it doesn't
  • Narrowing dies inside callbacks: after if (x), arr.map(() => x.foo) re-errors because TS assumes x may be reassigned — copy to a const before the callback
  • typeof x === "object" includes null — check x !== null in the same condition
  • Object.keys(obj) returns string[], not keyof typeof obj — intentional: structural types can carry extra keys. Cast only when you own the object literal
  • Array.isArray() on unknown narrows to any[] — element type needs its own check
  • in narrows unions only when the property appears in exactly one branch; on unlisted properties it narrows since TS 4.9
  • Destructured discriminants (const { kind } = action; if (kind === ...)) narrow only in TS >=4.6 — on older versions switch on action.kind directly

satisfies vs Annotation vs Cast

Three tools, three guarantees — pick by what you need to keep:

  • const x: T = v — checks AND widens to T: literal info gone, excess properties rejected
  • const x = v satisfies T (TS >=4.9) — checks compatibility, keeps the inferred narrow type: the default for config/route/theme objects
  • v as T — checks nothing beyond rough overlap; "hello" as number fails but {} as User compiles. It's an assertion, not a conversion

Strict Null Handling

  • ?? vs ||: port || 3000 turns a legitimate 0 into 3000; port ?? 3000 only replaces null/undefined. Same for "" in string options
  • Optional chaining ?. produces undefined, never null — APIs contracted to null need an explicit ?? null
  • Non-null ! is a cast in disguise — prefer early return or narrowing; each ! is a runtime crash candidate the compiler can no longer see

Strictness Beyond strict

strict: true is not the strictest configuration. Flags it does NOT include:

  • noUncheckedIndexedAccess (TS >=4.1) — arr[i] and record[key] become T | undefined. Catches the most common real crash (indexing off the end); enable on greenfield day one, on legacy expect a large error wave and stage it
  • exactOptionalPropertyTypes (TS >=4.4) — distinguishes "absent" from "explicitly undefined"; breaks code that assigns undefined to optional props
  • noPropertyAccessFromIndexSignature, noImplicitOverride — cheap to adopt, low churn
  • verbatimModuleSyntax (TS >=5.0) — forces import type for type-only imports; the modern replacement for isolatedModules import hygiene (modules.md)

Everything else about compiler configuration — target/lib, module × moduleResolution, paths, monorepos — lives in tsconfig.md; import/export mechanics and transpile-only compatibility in modules.md.

Where To Annotate

  • Default: annotate exported function signatures (parameters AND return), infer everything inside bodies (rule 3)
  • Explicit return types on the public surface also unlock isolatedDeclarations (TS >=5.5) and faster declaration emit
  • A type parameter must relate at least two positions (two params, or param and return). Used once, it's decoration — replace with unknown or the concrete type
  • Slow type checking has structural causes and a measurement workflow — performance.md

Output Gates

Before emitting TypeScript, verify:

  • No any introduced, explicit or implicit — unknown where the shape is genuinely open?
  • Exported functions annotated, parameters and return?
  • Every as has a runtime guard above it or a justifying comment?
  • Every switch over a union ends in an exhaustiveness check?
  • External data validated at runtime before its first typed use?
  • No ! where narrowing or ?? would prove it?

Configuration

User-dependent variables. Defaults apply until the user states a preference; store them in ~/Clawic/data/typescript/config.yaml.

Variable Type Default Effect
runtime_target node | browser | bun | deno | isomorphic node Drives module/moduleResolution and lib advice in tsconfig.md and import-extension rules in modules.md
project_kind app | library app Library flips guidance to declaration emit, exports-map types, and wider TS-version support; app defaults to skipLibCheck and bundler resolution
compile_pipeline tsc | transpile-only transpile-only transpile-only (esbuild, swc, Babel, bundlers) enforces the single-file-safe subset: no const enum, isolatedModules rules apply (modules.md); tsc lifts those bans
validation_library zod | valibot | arktype | ajv | none none Names the schema library used in boundary-validation examples (boundaries.md); none keeps guidance library-neutral
ts_version text (e.g. "5.4") latest Gates every TS >=X.Y-marked recommendation in this skill: below the mark, the pre-version workaround is offered instead (e.g. below 4.9 an annotation replaces satisfies)

Preference areas to record as the user reveals them:

  • conventionsinterface vs type default, type naming style, banned features (enums, namespaces, decorators), type-only import style
  • strictness posture — which beyond-strict flags to push proactively, cast tolerance, error-suppression policy
  • tooling — package manager, typescript-eslint strictness, monorepo layout (project references vs single config), type-test tooling
  • output format — quick fix vs explained fix, whether to show before/after diffs

Traps

Trap Why it fails Do instead
enum The one TS feature with runtime emit; const enum breaks under transpile-only compilers as const object + keyof typeof (modules.md)
Function or {} as a type Function accepts any callable and calls return any; {} accepts everything except null/undefined Exact signature (a: A) => R; Record<string, unknown> for bags
Trusting excess property checks as validation They fire only on fresh object literals — a value passed through a variable skips them Runtime validation at boundaries (boundaries.md)
// @ts-ignore Keeps suppressing after the error is fixed, hiding the next one // @ts-expect-error (TS >=3.9) — errors itself once stale
! to silence strictNullChecks Each one is an invisible cast and a crash candidate Narrow, early-return, or ??; sanctioned only for framework-assigned class fields (migration.md)
[key: string]: any to quiet index errors Poisons every access on the object with any Precise keys, Record<K, V>, or a Map; noUncheckedIndexedAccess (TS >=4.1) keeps even honest signatures honest

Where Experts Disagree

  • interface vs type. Default: interface for object shapes (merges for augmentation, caches relationships for faster checking), type for unions, functions, tuples, and mapped/conditional results. An existing codebase convention beats either.
  • Explicit return types everywhere vs boundary-only. Boundary-only is the default (rule 3). The everywhere school wins on published libraries and under isolatedDeclarations (TS >=5.5), where emit requires them anyway.
  • Runtime validation depth. Edges-only is the default — validating internally recomputes what the checker already proved. The schema-first school (schema as source of truth, static types inferred from it) wins when one shape crosses many boundaries (boundaries.md).

Related Skills

More Clawic skills, get them at https://clawic.com/skills/typescript (install if the user confirms):

  • javascript — runtime semantics: coercion, closures, event loop
  • react — typing components, hooks, and props in practice
  • nodejs — Node runtime, packaging, and ESM/CJS beyond the type layer
  • deno — TypeScript-native runtime without a build step

Feedback

Part of Clawic, the verified skill library. Get this skill: https://clawic.com/skills/typescript.