|
1 | 1 | --- |
2 | | -applyTo: "**/*.ts, **/*.tsx" |
| 2 | +applyTo: "**/*.ts,**/*.tsx" |
3 | 3 | --- |
4 | 4 |
|
5 | 5 | # TypeScript Conventions |
6 | 6 |
|
7 | 7 | TypeScript's type system is the primary tool for catching bugs before they reach production. Every rule here pushes toward maximum compiler coverage and self-documenting code. If the types are right, the code almost writes itself. |
8 | 8 |
|
9 | | -## Variables and Naming |
10 | | - |
11 | | -- Prefer `const` over `let` over `var`. |
12 | | -- Never use shortened or abbreviated names for variables, parameters, or properties. |
13 | | - - Use full descriptive names: `deltaX` not `dx`, `index` not `idx`, `event` not `e`, `previous` not `prev`, `direction` not `dir`, `position` not `pos`, `contextMenu` not `ctx`/`ctxMenu`. |
14 | | - - The only acceptable short names are well-established domain terms (e.g. `id`, `url`, `min`, `max`). |
15 | | -- Never leave unused import statements in the code. |
16 | | -- Always ensure the code compiles without warnings — run `yarn compile` to verify (no output means success). |
17 | | - |
18 | | -## Folder Structure and Naming |
19 | | - |
20 | | -- Do not prefix a file, component, type, or symbol with the name of its containing folder or concept — use folder structure to provide that context instead. |
21 | | -- Favor functional folder structure over technical folder structure. |
22 | | - - Group files by the feature or concept they belong to, not by their technical role. |
23 | | - - Avoid folders like `components/`, `hooks/`, `utils/`, `types/` at the feature level. |
24 | | - |
25 | 9 | ## Enums over Magic Strings |
26 | 10 |
|
27 | 11 | String literal unions look concise but provide no refactoring support, no namespace, and no discoverability. Enums give you all three — plus `switch` exhaustiveness checking. |
@@ -57,37 +41,9 @@ Each type gets its own file because it makes the codebase navigable — finding |
57 | 41 | `any` disables the compiler — the one tool that catches bugs for free. Every `any` is a hole in the safety net. Use `unknown` and narrow with type guards instead. |
58 | 42 |
|
59 | 43 | - Never use `any` — use `unknown`, `Record<string, unknown>`, or proper generic constraints. |
60 | | -- Use proper generic constraints: `<TCommand extends object = object>` not `= any`. |
61 | | -- Use `unknown` as the default generic parameter: `<T = unknown>` not `<T = any>`. |
62 | 44 | - Prefer `value as unknown as TargetType` over `value as any`. |
63 | | -- For objects with dynamic properties: `(obj as unknown as { prop: Type }).prop`. |
64 | | - |
65 | | -### Event Handlers |
66 | | - |
| 45 | +- Use `unknown` as default generic parameter instead of `any`. |
67 | 46 | - React synthetic events (`React.MouseEvent<Element, MouseEvent>`) and DOM events (`MouseEvent`) are different types — don't mix them. |
68 | | -- Convert between them: `nativeEvent as unknown as React.MouseEvent`. |
69 | | -- Use `e.preventDefault?.()` instead of `(e as any).preventDefault?.()`. |
70 | | - |
71 | | -### Generic React Components |
72 | | - |
73 | | -- Use `React.ComponentType<Props>` for React component types. |
74 | | -- For Storybook components with no props: `React.ComponentType<Record<string, never>>`. |
75 | | -- When converting component imports in Storybook: always `as unknown as` to avoid type mismatch errors. |
76 | | -- Properly type story args — never use `any`. |
77 | | - |
78 | | -### External Libraries |
79 | | - |
80 | | -- Use proper library types when available (e.g. PIXI types, not `any`). |
81 | | -- Use specific property types: `{ canvas?: HTMLCanvasElement }` instead of `any`. |
82 | | -- When library generics have strict constraints, thread types through `unknown`: `props.command as unknown as Constructor<Command<...>>`. |
83 | | -- Extract tuple results explicitly rather than destructuring when type assertions are needed. |
84 | | - |
85 | | -### Unknown Values |
86 | | - |
87 | | -- Add type guards before using function parameters of unknown type: `if (typeof accessor !== 'function') return ''`. |
88 | | -- Type parameters with fallbacks: `function<T = unknown>(accessor: ((obj: T) => unknown) | unknown)`. |
89 | | -- Cast unknown objects to record before property access: `(obj as Record<string, unknown>).items`. |
90 | | -- Use `String(value)` for string conversions; `new Date(value as string | number | Date)` for dates. |
91 | 47 |
|
92 | 48 | ## Localised Strings |
93 | 49 |
|
@@ -146,22 +102,6 @@ For attribute strings (e.g. `title`, `placeholder`, `aria-label`): |
146 | 102 | - **Never** use plain string literals for user-visible text in JSX or attribute props. This includes `label`, `header`, `placeholder`, `title`, `aria-label`, `emptyMessage`, and any visible text nodes. |
147 | 103 | - Only constant, non-localised values are allowed as raw strings (CSS class names, `key` props, internal identifiers). |
148 | 104 |
|
149 | | -## File Header |
150 | | - |
151 | | -Every TypeScript file must start with: |
152 | | - |
153 | | -```typescript |
154 | | -// Copyright (c) Cratis. All rights reserved. |
155 | | -// Licensed under the MIT license. See LICENSE file in the project root for full license information. |
156 | | -``` |
157 | | - |
158 | | -## Generated Files |
159 | | - |
160 | | -**Never edit generated files.** Files produced by the proxy generator (`dotnet build`), code scaffolding tools, or any other automated tool must not be modified by hand — in any language. Generated files are overwritten on the next build, so hand-edits are silently lost and create false confidence that a fix is in place. |
161 | | - |
162 | | -- If the generated output is wrong, fix the **source** (the C# record, the template, or the generator configuration) and rebuild. |
163 | | -- Generated TypeScript proxy files (commands, queries, observable queries) are always regenerated from C# sources. Never edit them directly. |
164 | | - |
165 | 105 | ## Arc Frontend Patterns |
166 | 106 |
|
167 | 107 | Arc's proxy generator bridges C# and TypeScript automatically — every `[Command]` and `[ReadModel]` becomes a TypeScript class with `.use()` hooks, `.execute()` methods, and change tracking. This is the foundation of full-stack type safety: change a C# record and the TypeScript proxy updates on the next `dotnet build`. |
@@ -202,3 +142,46 @@ Wraps multiple command-using components, aggregating their `hasChanges` state an |
202 | 142 | ### CommandForm |
203 | 143 |
|
204 | 144 | Declarative form component with built-in field types, validation timing (`validateOn: blur|change|both`), and automatic server-side validation feedback. |
| 145 | + |
| 146 | +## Variables and Naming |
| 147 | + |
| 148 | +- Prefer `const` over `let` over `var` when declaring variables. |
| 149 | +- Never use shortened or abbreviated names for variables, parameters, or properties. |
| 150 | + - Use full descriptive names: `deltaX` not `dx`, `index` not `idx`, `event` not `e`, `previous` not `prev`, `direction` not `dir`, `position` not `pos`, `contextMenu` not `ctx`/`ctxMenu`. |
| 151 | + - The only acceptable short names are well-established domain terms (e.g. `id`, `url`, `min`, `max`). |
| 152 | + |
| 153 | +## Imports and Compilation |
| 154 | + |
| 155 | +- Never leave unused import statements in the code. |
| 156 | +- Always ensure that the code compiles without warnings — use `yarn compile` to verify (successful runs produce no output). |
| 157 | +- Review each file for lint compliance before finalizing. |
| 158 | +- Never use placeholder or temporary types — use proper types from the start. |
| 159 | + |
| 160 | +## Folder Structure |
| 161 | + |
| 162 | +- Do not prefix a file, component, type, or symbol with the name of its containing folder or the concept it belongs to. Instead, use folder structure to provide that context. |
| 163 | +- Favor functional folder structure over technical folder structure. |
| 164 | + - Group files by the feature or concept they belong to, not by their technical role. |
| 165 | + - Avoid folders like `components/`, `hooks/`, `utils/`, `types/` at the feature level. |
| 166 | + |
| 167 | +## Advanced Type Safety |
| 168 | + |
| 169 | +Additional patterns for common tricky scenarios: |
| 170 | + |
| 171 | +**Storybook:** |
| 172 | +- Use `React.ComponentType<Record<string, never>>` for components with no props. |
| 173 | +- Always use `as unknown as` when converting component imports to avoid type mismatch errors. |
| 174 | +- Properly type story args — never use `any`. |
| 175 | + |
| 176 | +**External libraries with strict generic constraints:** |
| 177 | +- Import necessary types (e.g. `Command` from `@cratis/arc/commands`) rather than asserting to `any`. |
| 178 | +- Use type assertions through `unknown`: `props.command as unknown as Constructor<Command<...>>`. |
| 179 | +- Extract tuple results explicitly rather than destructuring when type assertions are needed. |
| 180 | +- Use proper library types when available; use specific property types (e.g. `{ canvas?: HTMLCanvasElement }`) over `any`. |
| 181 | + |
| 182 | +**Dynamic and generic types:** |
| 183 | +- Add type guards for unknown function parameters: `if (typeof accessor !== 'function') return ''`. |
| 184 | +- Type parameters with fallbacks: `function<T = unknown>(accessor: ((obj: T) => unknown) | unknown)`. |
| 185 | +- Cast arrays from `unknown` explicitly: `((obj as Record<string, unknown>).items || []) as string[]`. |
| 186 | +- Use `String(value)` for string conversions in generic contexts. |
| 187 | +- Use explicit Date parameter types: `new Date(value as string | number | Date)`. |
0 commit comments