Browse Source
Create TypeScript type safety guidelines enforcing strict typing across TimeSafari codebase. Includes special cases for Vue objects, dynamic component access, and framework integration where any types are necessary. - Enforce no-any rule with documented exceptions - Add Vue-specific edge cases (component instances, template refs, events) - Include third-party library and platform API handling - Provide migration checklists and code review guidelines - Document error handling patterns and anti-patterns - Add examples from existing codebasepull/152/head^2
1 changed files with 108 additions and 0 deletions
@ -0,0 +1,108 @@ |
|||||
|
--- |
||||
|
globs: **/src/**/*,**/scripts/**/*,**/electron/**/* |
||||
|
alwaysApply: false |
||||
|
--- |
||||
|
```json |
||||
|
{ |
||||
|
"coaching_level": "light", |
||||
|
"socratic_max_questions": 7, |
||||
|
"verbosity": "concise", |
||||
|
"timebox_minutes": null, |
||||
|
"format_enforcement": "strict" |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
# TypeScript Type Safety Guidelines |
||||
|
|
||||
|
**Author**: Matthew Raymer |
||||
|
**Date**: 2025-08-16 |
||||
|
**Status**: 🎯 **ACTIVE** |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
Practical rules to keep TypeScript strict and predictable. Minimize exceptions. |
||||
|
|
||||
|
## Core Rules |
||||
|
|
||||
|
1. **No `any`** |
||||
|
- Use explicit types. If unknown, use `unknown` and **narrow** via guards. |
||||
|
|
||||
|
2. **Error handling uses guards** |
||||
|
- Reuse guards from `src/interfaces/**` (e.g., `isDatabaseError`, `isApiError`). |
||||
|
- Catch with `unknown`; never cast to `any`. |
||||
|
|
||||
|
3. **Dynamic property access is type‑safe** |
||||
|
- Use `keyof` + `in` checks: |
||||
|
|
||||
|
```ts |
||||
|
obj[k as keyof typeof obj] |
||||
|
``` |
||||
|
|
||||
|
- Avoid `(obj as any)[k]`. |
||||
|
|
||||
|
## Minimal Special Cases (document in PR when used) |
||||
|
|
||||
|
- **Vue refs / instances**: Use `ComponentPublicInstance` or specific component |
||||
|
types for dynamic refs. |
||||
|
- **3rd‑party libs without types**: Narrow immediately to a **known interface**; |
||||
|
do not leave `any` hanging. |
||||
|
|
||||
|
## Patterns (short) |
||||
|
|
||||
|
### Database errors |
||||
|
|
||||
|
```ts |
||||
|
try { await this.$addContact(contact); } |
||||
|
catch (e: unknown) { |
||||
|
if (isDatabaseError(e) && e.message.includes("Key already exists")) { |
||||
|
/* handle duplicate */ |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### API errors |
||||
|
|
||||
|
```ts |
||||
|
try { await apiCall(); } |
||||
|
catch (e: unknown) { |
||||
|
if (isApiError(e)) { |
||||
|
const msg = e.response?.data?.error?.message; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### Dynamic keys |
||||
|
|
||||
|
```ts |
||||
|
const keys = Object.keys(newSettings).filter( |
||||
|
k => k in newSettings && newSettings[k as keyof typeof newSettings] !== undefined |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
## Checklists |
||||
|
|
||||
|
**Before commit** |
||||
|
|
||||
|
- [ ] No `any` (except documented, justified cases) |
||||
|
- [ ] Errors handled via guards |
||||
|
- [ ] Dynamic access uses `keyof`/`in` |
||||
|
- [ ] Imports point to correct interfaces/types |
||||
|
|
||||
|
**Code review** |
||||
|
|
||||
|
- [ ] Hunt hidden `as any` |
||||
|
- [ ] Guard‑based error paths verified |
||||
|
- [ ] Dynamic ops are type‑safe |
||||
|
- [ ] Prefer existing types over re‑inventing |
||||
|
|
||||
|
## Tools |
||||
|
|
||||
|
- `npm run lint-fix` — lint & auto‑fix |
||||
|
- `npm run type-check` — strict type compilation (CI + pre‑release) |
||||
|
- IDE: enable strict TS, ESLint/TS ESLint, Volar (Vue 3) |
||||
|
|
||||
|
## References |
||||
|
|
||||
|
- TS Handbook — https://www.typescriptlang.org/docs/ |
||||
|
- TS‑ESLint — https://typescript-eslint.io/rules/ |
||||
|
- Vue 3 + TS — https://vuejs.org/guide/typescript/ |
Loading…
Reference in new issue