@canonical/code-standards 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/.github/workflows/ci.yml +40 -0
- package/biome.json +6 -0
- package/bun.lock +200 -0
- package/data/code.ttl +208 -167
- package/data/css.ttl +110 -91
- package/data/icons.ttl +186 -150
- package/data/packaging.ttl +428 -170
- package/data/react.ttl +306 -244
- package/data/rust.ttl +563 -467
- package/data/storybook.ttl +108 -90
- package/data/styling.ttl +40 -40
- package/data/tsdoc.ttl +111 -86
- package/data/turtle.ttl +89 -68
- package/definitions/CodeStandard.ttl +28 -20
- package/docs/code.md +37 -327
- package/docs/css.md +20 -19
- package/docs/icons.md +37 -41
- package/docs/index.md +2 -1
- package/docs/packaging.md +643 -0
- package/docs/react.md +54 -58
- package/docs/rust.md +92 -158
- package/docs/storybook.md +18 -20
- package/docs/styling.md +8 -8
- package/docs/tsdoc.md +16 -16
- package/docs/turtle.md +15 -15
- package/package.json +16 -2
- package/skills/add-standard/SKILL.md +83 -47
- package/src/scripts/generate-docs.ts +95 -13
- package/src/scripts/index.ts +4 -2
- package/tsconfig.json +8 -0
package/data/code.ttl
CHANGED
|
@@ -15,16 +15,19 @@ cs:FunctionPurity a cs:CodeStandard ;
|
|
|
15
15
|
cs:name "code/function/purity" ;
|
|
16
16
|
cs:hasCategory cs:CodeCategory ;
|
|
17
17
|
cs:description "Functions should be pure where possible. A pure function returns the same output for the same input and does not modify external state or perform I/O operations. If a function must perform side effects (e.g., I/O in CLI tools), it must be explicitly annotated and documented with the reason for impurity using the @note tag in TSDoc/JSDoc." ;
|
|
18
|
-
cs:
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
cs:do [
|
|
19
|
+
cs:description "Create pure functions that rely only on their inputs to compute the output." ;
|
|
20
|
+
cs:language "typescript" ;
|
|
21
|
+
cs:code """
|
|
21
22
|
const calculatePrice = (basePrice: number, taxRate: number): number => {
|
|
22
23
|
return basePrice * (1 + taxRate);
|
|
23
24
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
"""
|
|
26
|
+
] ;
|
|
27
|
+
cs:do [
|
|
28
|
+
cs:description "Annotate and document impure functions that perform I/O or modify external state, explaining why impurity is necessary using @note." ;
|
|
29
|
+
cs:language "typescript" ;
|
|
30
|
+
cs:code """
|
|
28
31
|
/**
|
|
29
32
|
* Writes data to the file system.
|
|
30
33
|
* @note - This function is impure - it modifies the file system.
|
|
@@ -32,41 +35,47 @@ const calculatePrice = (basePrice: number, taxRate: number): number => {
|
|
|
32
35
|
function writeOutputToFile(data: string, path: string) {
|
|
33
36
|
fs.writeFileSync(path, data);
|
|
34
37
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
cs:
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
"""
|
|
39
|
+
] ;
|
|
40
|
+
cs:dont [
|
|
41
|
+
cs:description "Create impure functions without annotation or documentation." ;
|
|
42
|
+
cs:language "typescript" ;
|
|
43
|
+
cs:code """
|
|
40
44
|
let total = 0;
|
|
41
45
|
const addToTotal = (value: number) => {
|
|
42
46
|
total += value; // Modifies external state, not documented
|
|
43
47
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
"""
|
|
49
|
+
] ;
|
|
50
|
+
cs:dont [
|
|
51
|
+
cs:description "Hide side effects in functions that appear pure." ;
|
|
52
|
+
cs:language "typescript" ;
|
|
53
|
+
cs:code """
|
|
48
54
|
function getConfig() {
|
|
49
55
|
// Reads from disk without annotation
|
|
50
56
|
return fs.readFileSync('config.json');
|
|
51
57
|
}
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
"""
|
|
59
|
+
] .
|
|
54
60
|
|
|
55
61
|
# Function Composition Standard
|
|
56
62
|
cs:FunctionComposition a cs:CodeStandard ;
|
|
57
63
|
cs:name "code/function/composition" ;
|
|
58
64
|
cs:hasCategory cs:CodeCategory ;
|
|
59
65
|
cs:description "Each function must handle exactly one specific task. Complex operations must be broken down into smaller, single-purpose functions." ;
|
|
60
|
-
cs:
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
cs:do [
|
|
67
|
+
cs:description "Define functions with a single, clear responsibility." ;
|
|
68
|
+
cs:language "typescript" ;
|
|
69
|
+
cs:code """
|
|
63
70
|
const validateEmail = (email: string): boolean => {
|
|
64
71
|
return /^[^@]+@[^@]+\\.[^@]+$/.test(email);
|
|
65
72
|
};
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
"""
|
|
74
|
+
] ;
|
|
75
|
+
cs:do [
|
|
76
|
+
cs:description "Compose single-purpose functions to build complex logic." ;
|
|
77
|
+
cs:language "typescript" ;
|
|
78
|
+
cs:code """
|
|
70
79
|
const validatePassword = (password: string): boolean => {
|
|
71
80
|
return password.length >= 8;
|
|
72
81
|
};
|
|
@@ -74,28 +83,30 @@ const validatePassword = (password: string): boolean => {
|
|
|
74
83
|
const validateForm = (email: string, password:string): boolean => {
|
|
75
84
|
return validateEmail(email) && validatePassword(password);
|
|
76
85
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
cs:
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
"""
|
|
87
|
+
] ;
|
|
88
|
+
cs:dont [
|
|
89
|
+
cs:description "Mix multiple responsibilities in a single function." ;
|
|
90
|
+
cs:language "typescript" ;
|
|
91
|
+
cs:code """
|
|
82
92
|
const processUserData = (user: any) => {
|
|
83
93
|
// Validates user data
|
|
84
94
|
// Transforms the data
|
|
85
95
|
// Saves to the database
|
|
86
96
|
// Sends a notification
|
|
87
97
|
};
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
"""
|
|
99
|
+
] .
|
|
90
100
|
|
|
91
101
|
# Function Location Standard
|
|
92
102
|
cs:FunctionLocation a cs:CodeStandard ;
|
|
93
103
|
cs:name "code/function/location" ;
|
|
94
104
|
cs:hasCategory cs:CodeCategory ;
|
|
95
105
|
cs:description "Functions must be defined as close to their broadest point of use as possible. Shared utilities must be placed in a common location, and each function must serve a single feature or responsibility." ;
|
|
96
|
-
cs:
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
cs:do [
|
|
107
|
+
cs:description "Place functions in the file or module where they are most broadly used. If a function is shared across multiple components or modules, place it in a common utility location." ;
|
|
108
|
+
cs:language "typescript" ;
|
|
109
|
+
cs:code """
|
|
99
110
|
// utils/math.ts
|
|
100
111
|
export function calculateSum(a: number, b: number): number {
|
|
101
112
|
return a + b;
|
|
@@ -105,22 +116,12 @@ export function calculateSum(a: number, b: number): number {
|
|
|
105
116
|
import { calculateSum } from './utils/math.js';
|
|
106
117
|
|
|
107
118
|
// For single-use functions, keep them close to their usage:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
...user,
|
|
116
|
-
formattedName,
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
""" ;
|
|
121
|
-
cs:donts """
|
|
122
|
-
(Don't) Place functions far from where they are used, or in a generic location if only used in one place.
|
|
123
|
-
```typescript
|
|
119
|
+
"""
|
|
120
|
+
] ;
|
|
121
|
+
cs:dont [
|
|
122
|
+
cs:description "Place functions far from where they are used, or in a generic location if only used in one place." ;
|
|
123
|
+
cs:language "typescript" ;
|
|
124
|
+
cs:code """
|
|
124
125
|
// utils/validators.ts
|
|
125
126
|
export const validateUser = (user: User) => { ... }; // Only used in one component
|
|
126
127
|
|
|
@@ -128,19 +129,18 @@ export const validateUser = (user: User) => { ... }; // Only used in one compone
|
|
|
128
129
|
import { validateUser } from '../utils/validators.js'; // Less ideal if only used here
|
|
129
130
|
|
|
130
131
|
// Don't mix unrelated functions in a single file:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
export const stringFunc = () => {};
|
|
134
|
-
""" .
|
|
132
|
+
"""
|
|
133
|
+
] .
|
|
135
134
|
|
|
136
135
|
# API Stability Standard
|
|
137
136
|
cs:APIStability a cs:CodeStandard ;
|
|
138
137
|
cs:name "code/api/stability" ;
|
|
139
138
|
cs:hasCategory cs:CodeCategory ;
|
|
140
139
|
cs:description "Experimental APIs must be marked with the @experimental JSDoc tag in type definition files. The tag must include a description of what is experimental." ;
|
|
141
|
-
cs:
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
cs:do [
|
|
141
|
+
cs:description "Mark an experimental interface with a clear @experimental JSDoc tag and description." ;
|
|
142
|
+
cs:language "typescript" ;
|
|
143
|
+
cs:code """
|
|
144
144
|
/**
|
|
145
145
|
* Configuration for the data processing pipeline.
|
|
146
146
|
* @experimental The streaming API is experimental and may change
|
|
@@ -149,10 +149,12 @@ cs:APIStability a cs:CodeStandard ;
|
|
|
149
149
|
interface PipelineConfig {
|
|
150
150
|
// ...
|
|
151
151
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
"""
|
|
153
|
+
] ;
|
|
154
|
+
cs:do [
|
|
155
|
+
cs:description "Add an @experimental tag to a specific property with context about its experimental status." ;
|
|
156
|
+
cs:language "typescript" ;
|
|
157
|
+
cs:code """
|
|
156
158
|
interface PipelineConfig {
|
|
157
159
|
/**
|
|
158
160
|
* @experimental The custom transformers API is in beta, and the interface
|
|
@@ -160,10 +162,12 @@ interface PipelineConfig {
|
|
|
160
162
|
*/
|
|
161
163
|
transformers?: DataTransformer[];
|
|
162
164
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
"""
|
|
166
|
+
] ;
|
|
167
|
+
cs:do [
|
|
168
|
+
cs:description "Describe the experimental status and any future plans for the API." ;
|
|
169
|
+
cs:language "typescript" ;
|
|
170
|
+
cs:code """
|
|
167
171
|
interface CacheConfig {
|
|
168
172
|
/**
|
|
169
173
|
* @experimental The distributed cache API is experimental and will be
|
|
@@ -171,46 +175,52 @@ interface CacheConfig {
|
|
|
171
175
|
*/
|
|
172
176
|
distributed?: boolean;
|
|
173
177
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
cs:
|
|
177
|
-
|
|
178
|
-
|
|
178
|
+
"""
|
|
179
|
+
] ;
|
|
180
|
+
cs:dont [
|
|
181
|
+
cs:description "Use the @experimental tag without an explanation." ;
|
|
182
|
+
cs:language "typescript" ;
|
|
183
|
+
cs:code """
|
|
179
184
|
interface ProcessorConfig {
|
|
180
185
|
/** @experimental */ // Bad: No context provided.
|
|
181
186
|
streaming?: boolean;
|
|
182
187
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
"""
|
|
189
|
+
] ;
|
|
190
|
+
cs:dont [
|
|
191
|
+
cs:description "Use the @experimental tag without describing what is experimental." ;
|
|
192
|
+
cs:language "typescript" ;
|
|
193
|
+
cs:code """
|
|
187
194
|
/**
|
|
188
195
|
* @experimental // Bad: No description of what is experimental.
|
|
189
196
|
*/
|
|
190
197
|
interface QueueConfig {
|
|
191
198
|
processor: (item: any) => Promise<void>;
|
|
192
199
|
}
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
"""
|
|
201
|
+
] .
|
|
195
202
|
|
|
196
203
|
# Function Verb Naming Standard
|
|
197
204
|
cs:FunctionVerbNaming a cs:CodeStandard ;
|
|
198
205
|
cs:name "code/naming/function-verb" ;
|
|
199
206
|
cs:hasCategory cs:CodeCategory ;
|
|
200
207
|
cs:description "All function and method names must start with a verb that describes the action the function performs. Names like `pathToX` or `dataForUser` describe a result, not an action — use `findPathToX` or `fetchDataForUser` instead. This makes the function's intent immediately clear and distinguishes functions from plain values or constants." ;
|
|
201
|
-
cs:
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
cs:do [
|
|
209
|
+
cs:description "Start function names with an action verb" ;
|
|
210
|
+
cs:language "typescript" ;
|
|
211
|
+
cs:code """
|
|
204
212
|
const findPathToModule = (name: string): string => { /* ... */ };
|
|
205
213
|
const fetchDataForUser = (userId: string): Promise<User> => { /* ... */ };
|
|
206
214
|
const parseConfigFile = (path: string): Config => { /* ... */ };
|
|
207
215
|
const calculateTotalPrice = (items: Item[]): number => { /* ... */ };
|
|
208
216
|
const isValidEmail = (email: string): boolean => { /* ... */ };
|
|
209
217
|
const hasPermission = (user: User, action: string): boolean => { /* ... */ };
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
"""
|
|
219
|
+
] ;
|
|
220
|
+
cs:do [
|
|
221
|
+
cs:description "Use descriptive verb prefixes that convey the operation" ;
|
|
222
|
+
cs:language "typescript" ;
|
|
223
|
+
cs:code """
|
|
214
224
|
// Retrieval: get, fetch, find, resolve, load, read
|
|
215
225
|
const getUser = (id: string): User => { /* ... */ };
|
|
216
226
|
const fetchOrders = (): Promise<Order[]> => { /* ... */ };
|
|
@@ -232,11 +242,12 @@ const removeItem = (id: string): void => { /* ... */ };
|
|
|
232
242
|
// Creation: create, build, generate, compose, make
|
|
233
243
|
const createConnection = (config: Config): Connection => { /* ... */ };
|
|
234
244
|
const buildQuery = (params: Params): string => { /* ... */ };
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
cs:
|
|
238
|
-
|
|
239
|
-
|
|
245
|
+
"""
|
|
246
|
+
] ;
|
|
247
|
+
cs:dont [
|
|
248
|
+
cs:description "Name functions as nouns or noun phrases — they read like values, not actions" ;
|
|
249
|
+
cs:language "typescript" ;
|
|
250
|
+
cs:code """
|
|
240
251
|
// Bad: reads like a variable, not a function
|
|
241
252
|
const pathToModule = (name: string): string => { /* ... */ };
|
|
242
253
|
// Good: findPathToModule
|
|
@@ -246,35 +257,39 @@ const dataForUser = (userId: string): Promise<User> => { /* ... */ };
|
|
|
246
257
|
|
|
247
258
|
const userPermissions = (user: User): Permission[] => { /* ... */ };
|
|
248
259
|
// Good: getPermissions or listPermissions
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
260
|
+
"""
|
|
261
|
+
] ;
|
|
262
|
+
cs:dont [
|
|
263
|
+
cs:description "Use vague or non-descriptive verbs that don't convey meaning" ;
|
|
264
|
+
cs:language "typescript" ;
|
|
265
|
+
cs:code """
|
|
253
266
|
// Bad: "do" and "process" are too vague on their own
|
|
254
267
|
const doEmail = (email: string) => { /* ... */ };
|
|
255
268
|
// Good: sendEmail, validateEmail, formatEmail
|
|
256
269
|
|
|
257
270
|
const processUser = (user: User) => { /* ... */ };
|
|
258
271
|
// Good: activateUser, deactivateUser, updateUser
|
|
259
|
-
|
|
260
|
-
|
|
272
|
+
"""
|
|
273
|
+
] .
|
|
261
274
|
|
|
262
275
|
# Constants File Standard
|
|
263
276
|
cs:ConstantsFile a cs:CodeStandard ;
|
|
264
277
|
cs:name "code/constants/file" ;
|
|
265
278
|
cs:hasCategory cs:CodeCategory ;
|
|
266
279
|
cs:description "Domain constants must be collected in a `constants.ts` file using named exports (no default export). The file must be colocated at the level of the domain that owns the constants — inside the domain folder when one exists. Constants files always use named exports so consumers can import exactly what they need. Single-use constants that are only referenced in one file may be declared inline in that file instead of being extracted to `constants.ts`." ;
|
|
267
|
-
cs:
|
|
268
|
-
|
|
269
|
-
|
|
280
|
+
cs:do [
|
|
281
|
+
cs:description "Create a `constants.ts` file with named exports at the domain level" ;
|
|
282
|
+
cs:language "typescript" ;
|
|
283
|
+
cs:code """
|
|
270
284
|
// features/pricing/constants.ts
|
|
271
285
|
export const DEFAULT_CURRENCY = "USD";
|
|
272
286
|
export const TAX_RATE = 0.21;
|
|
273
287
|
export const MAX_DISCOUNT_PERCENT = 50;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
288
|
+
"""
|
|
289
|
+
] ;
|
|
290
|
+
cs:do [
|
|
291
|
+
cs:description "Colocate `constants.ts` inside the domain folder it belongs to" ;
|
|
292
|
+
cs:code """
|
|
278
293
|
features/
|
|
279
294
|
├── pricing/
|
|
280
295
|
│ ├── constants.ts # Domain constants live here
|
|
@@ -284,24 +299,30 @@ features/
|
|
|
284
299
|
│ ├── constants.ts # Auth-specific constants
|
|
285
300
|
│ ├── validateToken.ts
|
|
286
301
|
│ └── index.ts
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
302
|
+
"""
|
|
303
|
+
] ;
|
|
304
|
+
cs:do [
|
|
305
|
+
cs:description "Import constants directly from the owning domain's `constants.ts`" ;
|
|
306
|
+
cs:language "typescript" ;
|
|
307
|
+
cs:code """
|
|
291
308
|
// features/pricing/calculatePrice.ts
|
|
292
309
|
import { TAX_RATE, DEFAULT_CURRENCY } from "./constants.js";
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
310
|
+
"""
|
|
311
|
+
] ;
|
|
312
|
+
cs:do [
|
|
313
|
+
cs:description "Keep all exports in `constants.ts` as named exports with the same shape (plain values)" ;
|
|
314
|
+
cs:language "typescript" ;
|
|
315
|
+
cs:code """
|
|
297
316
|
// constants.ts
|
|
298
317
|
export const RECONNECT_INTERVAL_MS = 5000;
|
|
299
318
|
export const MAX_RETRIES = 3;
|
|
300
319
|
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
320
|
+
"""
|
|
321
|
+
] ;
|
|
322
|
+
cs:do [
|
|
323
|
+
cs:description "Keep single-use constants inline in the file that uses them" ;
|
|
324
|
+
cs:language "typescript" ;
|
|
325
|
+
cs:code """
|
|
305
326
|
// features/pricing/applyDiscount.ts
|
|
306
327
|
const MAX_DISCOUNT_PERCENT = 50;
|
|
307
328
|
|
|
@@ -309,53 +330,61 @@ export default function applyDiscount(price: number, rate: number): number {
|
|
|
309
330
|
const capped = Math.min(rate, MAX_DISCOUNT_PERCENT / 100);
|
|
310
331
|
return price * (1 - capped);
|
|
311
332
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
cs:
|
|
315
|
-
|
|
316
|
-
|
|
333
|
+
"""
|
|
334
|
+
] ;
|
|
335
|
+
cs:dont [
|
|
336
|
+
cs:description "Use a default export in a constants file" ;
|
|
337
|
+
cs:language "typescript" ;
|
|
338
|
+
cs:code """
|
|
317
339
|
// Bad: default export forces consumers to name the import
|
|
318
340
|
export default {
|
|
319
341
|
DEFAULT_CURRENCY: "USD",
|
|
320
342
|
TAX_RATE: 0.21,
|
|
321
343
|
};
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
344
|
+
"""
|
|
345
|
+
] ;
|
|
346
|
+
cs:dont [
|
|
347
|
+
cs:description "Scatter constants across unrelated files instead of collecting them in `constants.ts`" ;
|
|
348
|
+
cs:language "typescript" ;
|
|
349
|
+
cs:code """
|
|
326
350
|
// Bad: constants buried inside implementation files
|
|
327
351
|
// calculatePrice.ts
|
|
328
352
|
export const TAX_RATE = 0.21;
|
|
329
353
|
const calculatePrice = (base: number) => base * (1 + TAX_RATE);
|
|
330
354
|
export default calculatePrice;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
355
|
+
"""
|
|
356
|
+
] ;
|
|
357
|
+
cs:dont [
|
|
358
|
+
cs:description "Place constants far from the domain that owns them" ;
|
|
359
|
+
cs:language "typescript" ;
|
|
360
|
+
cs:code """
|
|
335
361
|
// Bad: pricing constants in a top-level shared file
|
|
336
362
|
// src/constants.ts
|
|
337
363
|
export const TAX_RATE = 0.21; // Belongs in pricing/
|
|
338
364
|
export const MAX_RETRIES = 3; // Belongs in network/
|
|
339
365
|
export const SESSION_TTL_MS = 3600; // Belongs in auth/
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
366
|
+
"""
|
|
367
|
+
] ;
|
|
368
|
+
cs:dont [
|
|
369
|
+
cs:description "Mix constants with functions, types, or other non-constant exports" ;
|
|
370
|
+
cs:language "typescript" ;
|
|
371
|
+
cs:code """
|
|
344
372
|
// Bad: constants.ts should only contain constant values
|
|
345
373
|
export const MAX_RETRIES = 3;
|
|
346
374
|
export const formatRetryMessage = (n: number) => `Retry ${n} of ${MAX_RETRIES}`;
|
|
347
375
|
export type RetryConfig = { max: number };
|
|
348
|
-
|
|
349
|
-
|
|
376
|
+
"""
|
|
377
|
+
] .
|
|
350
378
|
|
|
351
379
|
# Types File Standard
|
|
352
380
|
cs:TypesFile a cs:CodeStandard ;
|
|
353
381
|
cs:name "code/types/file" ;
|
|
354
382
|
cs:hasCategory cs:CodeCategory ;
|
|
355
383
|
cs:description "Reusable domain types must be collected in a `types.ts` file using named exports (no default export), colocated at the level of the domain that owns them. Types that are only used in a single file may be declared inline in that file instead of being extracted to `types.ts`." ;
|
|
356
|
-
cs:
|
|
357
|
-
|
|
358
|
-
|
|
384
|
+
cs:do [
|
|
385
|
+
cs:description "Create a `types.ts` file with named exports for types shared across the domain" ;
|
|
386
|
+
cs:language "typescript" ;
|
|
387
|
+
cs:code """
|
|
359
388
|
// features/pricing/types.ts
|
|
360
389
|
export type Currency = "USD" | "EUR" | "GBP";
|
|
361
390
|
export interface PriceBreakdown {
|
|
@@ -364,10 +393,11 @@ export interface PriceBreakdown {
|
|
|
364
393
|
total: number;
|
|
365
394
|
}
|
|
366
395
|
export type DiscountStrategy = "percentage" | "fixed";
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
396
|
+
"""
|
|
397
|
+
] ;
|
|
398
|
+
cs:do [
|
|
399
|
+
cs:description "Colocate `types.ts` inside the domain folder it belongs to" ;
|
|
400
|
+
cs:code """
|
|
371
401
|
features/
|
|
372
402
|
├── pricing/
|
|
373
403
|
│ ├── types.ts # Shared domain types live here
|
|
@@ -378,10 +408,12 @@ features/
|
|
|
378
408
|
│ ├── types.ts # Auth-specific types
|
|
379
409
|
│ ├── validateToken.ts
|
|
380
410
|
│ └── index.ts
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
411
|
+
"""
|
|
412
|
+
] ;
|
|
413
|
+
cs:do [
|
|
414
|
+
cs:description "Keep single-use types inline in the file that uses them" ;
|
|
415
|
+
cs:language "typescript" ;
|
|
416
|
+
cs:code """
|
|
385
417
|
// features/pricing/applyDiscount.ts
|
|
386
418
|
type DiscountResult = { discounted: number; savings: number };
|
|
387
419
|
|
|
@@ -389,49 +421,58 @@ export default function applyDiscount(price: number, rate: number): DiscountResu
|
|
|
389
421
|
const savings = price * rate;
|
|
390
422
|
return { discounted: price - savings, savings };
|
|
391
423
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
424
|
+
"""
|
|
425
|
+
] ;
|
|
426
|
+
cs:do [
|
|
427
|
+
cs:description "Import shared types from the owning domain's `types.ts`" ;
|
|
428
|
+
cs:language "typescript" ;
|
|
429
|
+
cs:code """
|
|
396
430
|
// features/pricing/calculatePrice.ts
|
|
397
431
|
import type { Currency, PriceBreakdown } from "./types.js";
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
cs:
|
|
401
|
-
|
|
402
|
-
|
|
432
|
+
"""
|
|
433
|
+
] ;
|
|
434
|
+
cs:dont [
|
|
435
|
+
cs:description "Use a default export in a types file" ;
|
|
436
|
+
cs:language "typescript" ;
|
|
437
|
+
cs:code """
|
|
403
438
|
// Bad: default export for a collection of types
|
|
404
439
|
export default interface PriceBreakdown {
|
|
405
440
|
base: number;
|
|
406
441
|
tax: number;
|
|
407
442
|
total: number;
|
|
408
443
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
444
|
+
"""
|
|
445
|
+
] ;
|
|
446
|
+
cs:dont [
|
|
447
|
+
cs:description "Extract single-use types to `types.ts` when they are only used in one file" ;
|
|
448
|
+
cs:language "typescript" ;
|
|
449
|
+
cs:code """
|
|
413
450
|
// Bad: types.ts contains a type only used in applyDiscount.ts
|
|
414
451
|
// types.ts
|
|
415
452
|
export type DiscountResult = { discounted: number; savings: number };
|
|
416
453
|
|
|
417
454
|
// applyDiscount.ts
|
|
418
455
|
import type { DiscountResult } from "./types.js"; // Unnecessary indirection
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
456
|
+
"""
|
|
457
|
+
] ;
|
|
458
|
+
cs:dont [
|
|
459
|
+
cs:description "Place types far from the domain that owns them" ;
|
|
460
|
+
cs:language "typescript" ;
|
|
461
|
+
cs:code """
|
|
423
462
|
// Bad: all types dumped in a top-level shared file
|
|
424
463
|
// src/types.ts
|
|
425
464
|
export type Currency = "USD" | "EUR" | "GBP"; // Belongs in pricing/
|
|
426
465
|
export type AuthToken = { jwt: string }; // Belongs in auth/
|
|
427
466
|
export type RetryPolicy = { max: number }; // Belongs in network/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
467
|
+
"""
|
|
468
|
+
] ;
|
|
469
|
+
cs:dont [
|
|
470
|
+
cs:description "Mix types with functions, constants, or other non-type exports" ;
|
|
471
|
+
cs:language "typescript" ;
|
|
472
|
+
cs:code """
|
|
432
473
|
// Bad: types.ts should only contain type declarations
|
|
433
474
|
export type Currency = "USD" | "EUR" | "GBP";
|
|
434
475
|
export const DEFAULT_CURRENCY: Currency = "USD";
|
|
435
476
|
export const formatCurrency = (value: number, currency: Currency) => `${currency} ${value}`;
|
|
436
|
-
|
|
437
|
-
|
|
477
|
+
"""
|
|
478
|
+
] .
|