consistent-aliases
npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill consistent-aliases
Agent 安装分布
Skill 文档
Be Consistent in Your Use of Aliases
Overview
If you create an alias, use it consistently.
When you assign an object property to a variable, you create an alias. TypeScript tracks them separately, so narrowing one doesn’t narrow the other. Choose one and stick with it.
When to Use This Skill
- Type narrowing isn’t working as expected
- Using both a variable and its source property
- Property checks not affecting aliased variables
- Refinements being “lost” after function calls
The Iron Rule
One alias OR the original - never mix them in the same scope.
Prefer destructuring for consistent naming.
Remember:
- Aliases and originals are tracked separately
- Narrowing one doesn’t narrow the other
- Function calls can invalidate property refinements
- Local variables are safer than object properties
Detection: Alias Breaks Narrowing
interface Polygon {
exterior: Coordinate[];
bbox?: BoundingBox;
}
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox; // Created an alias
if (polygon.bbox) { // Narrows polygon.bbox
if (pt.x < box.x[0]) { // box is still possibly undefined!
// ~~~
// 'box' is possibly 'undefined'
}
}
}
The check on polygon.bbox doesn’t narrow box.
The Golden Rule: Use Aliases Consistently
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const box = polygon.bbox;
if (box) { // Check the alias
if (pt.x < box.x[0]) { // Use the alias
// OK - box is narrowed here
}
}
}
Now both the check and usage refer to the same variable.
Best Practice: Use Destructuring
function isPointInPolygon(polygon: Polygon, pt: Coordinate) {
const { bbox } = polygon; // Same name as property
if (bbox) {
const { x, y } = bbox; // Continue destructuring
if (pt.x < x[0] || pt.x > x[1] || pt.y < y[0] || pt.y > y[1]) {
return false;
}
}
return true;
}
Benefits:
- Consistent naming (no
boxvsbboxconfusion) - More concise
- Works well with TypeScript’s control flow
Aliases at Runtime Too
Aliasing affects runtime behavior:
const { bbox } = polygon;
if (!bbox) {
calculatePolygonBbox(polygon); // Fills in polygon.bbox
// Now polygon.bbox exists, but bbox is still undefined!
}
The alias and original can diverge at runtime.
Function Calls and Refinements
TypeScript makes a pragmatic choice about function calls:
function expandPolygon(p: Polygon) { /* ... */ }
if (polygon.bbox) {
polygon.bbox // BoundingBox (narrowed)
expandPolygon(polygon);
polygon.bbox // Still BoundingBox (TypeScript trusts you)
// But the function might have set it to undefined!
}
TypeScript assumes functions don’t invalidate refinements. This is usually fine but can be wrong.
Safer: Local Variables
if (polygon.bbox) {
const bbox = polygon.bbox; // Capture it locally
expandPolygon(polygon);
bbox // Still BoundingBox - local variable is safe
}
Local variables can’t be changed by function calls.
Readonly for Extra Safety
function safeExpand(p: Readonly<Polygon>) {
// Can't modify p.bbox
}
if (polygon.bbox) {
safeExpand(polygon);
polygon.bbox // Guaranteed still BoundingBox
}
Readonly parameters prevent mutation concerns.
Common Patterns
Nullish Coalescing Instead of Alias
// Instead of:
const name = person.nickname;
const displayName = name ? name : person.fullName;
// Use:
const displayName = person.nickname ?? person.fullName;
Map.get Pattern
// TypeScript doesn't connect has() and get():
if (map.has(key)) {
const value = map.get(key); // Still T | undefined
}
// Better pattern:
const value = map.get(key);
if (value !== undefined) {
value // T (narrowed)
}
// Or with nullish coalescing:
const value = map.get(key) ?? defaultValue;
Pressure Resistance Protocol
1. “I Need Both Names”
Pressure: “Sometimes I use the property, sometimes the variable”
Response: Pick one. Mixing them breaks narrowing.
Action: Use destructuring for consistent naming.
2. “The Function Won’t Modify It”
Pressure: “I know expandPolygon doesn’t touch bbox”
Response: TypeScript can’t know that. Document with Readonly.
Action: Use local variables or readonly parameters for safety.
Red Flags – STOP and Reconsider
- Variable created from property, then property used in condition
- Type errors about “possibly undefined” after an
ifcheck - Mixing property access and variable use in the same block
- Relying on refinements after function calls
Common Rationalizations (All Invalid)
| Excuse | Reality |
|---|---|
| “The alias is the same thing” | TypeScript tracks them separately |
| “I already checked the property” | Check doesn’t narrow the alias |
| “Functions won’t mutate it” | TypeScript can’t verify that |
Quick Reference
// DON'T: Mix alias and original
const box = polygon.bbox;
if (polygon.bbox) { // Checks original
box.x; // Uses alias - still possibly undefined!
}
// DO: Use alias consistently
const box = polygon.bbox;
if (box) { // Checks alias
box.x; // Uses alias - narrowed correctly
}
// DO: Use destructuring
const { bbox } = polygon;
if (bbox) {
const { x, y } = bbox;
}
The Bottom Line
Choose one name and use it consistently.
When you alias a property, TypeScript tracks the alias and original separately. Narrowing one doesn’t narrow the other. Use destructuring for consistent naming, and prefer local variables when function calls might invalidate refinements.
Reference
Based on “Effective TypeScript” by Dan Vanderkam, Item 23: Be Consistent in Your Use of Aliases.