@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/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:dos """
19
- (Do) Create pure functions that rely only on their inputs to compute the output.
20
- ```typescript
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
- (Do) Annotate and document impure functions that perform I/O or modify external state, explaining why impurity is necessary using @note.
27
- ```typescript
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:donts """
38
- (Don't) Create impure functions without annotation or documentation.
39
- ```typescript
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
- (Don't) Hide side effects in functions that appear pure.
47
- ```typescript
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:dos """
61
- (Do) Define functions with a single, clear responsibility.
62
- ```typescript
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
- (Do) Compose single-purpose functions to build complex logic.
69
- ```typescript
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:donts """
80
- (Don't) Mix multiple responsibilities in a single function.
81
- ```typescript
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:dos """
97
- (Do) 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.
98
- ```typescript
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
- ```typescript
109
- // services/UserService.ts
110
- const formatUserName = (user: User) => `${user.firstName} ${user.lastName}`;
111
-
112
- const getProfileData = (user: User) => {
113
- const formattedName = formatUserName(user);
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
- ```typescript
132
- export const mathFunc = () => {};
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:dos """
142
- (Do) Mark an experimental interface with a clear @experimental JSDoc tag and description.
143
- ```typescript
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
- (Do) Add an @experimental tag to a specific property with context about its experimental status.
155
- ```typescript
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
- (Do) Describe the experimental status and any future plans for the API.
166
- ```typescript
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:donts """
177
- (Don't) Use the @experimental tag without an explanation.
178
- ```typescript
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
- (Don't) Use the @experimental tag without describing what is experimental.
186
- ```typescript
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:dos """
202
- (Do) Start function names with an action verb:
203
- ```typescript
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
- (Do) Use descriptive verb prefixes that convey the operation:
213
- ```typescript
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:donts """
238
- (Don't) Name functions as nouns or noun phrases — they read like values, not actions:
239
- ```typescript
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
- (Don't) Use vague or non-descriptive verbs that don't convey meaning:
252
- ```typescript
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:dos """
268
- (Do) Create a `constants.ts` file with named exports at the domain level:
269
- ```typescript
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
- (Do) Colocate `constants.ts` inside the domain folder it belongs to:
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
- (Do) Import constants directly from the owning domain's `constants.ts`:
290
- ```typescript
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
- (Do) Keep all exports in `constants.ts` as named exports with the same shape (plain values):
296
- ```typescript
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
- (Do) Keep single-use constants inline in the file that uses them:
304
- ```typescript
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:donts """
315
- (Don't) Use a default export in a constants file:
316
- ```typescript
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
- (Don't) Scatter constants across unrelated files instead of collecting them in `constants.ts`:
325
- ```typescript
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
- (Don't) Place constants far from the domain that owns them:
334
- ```typescript
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
- (Don't) Mix constants with functions, types, or other non-constant exports:
343
- ```typescript
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:dos """
357
- (Do) Create a `types.ts` file with named exports for types shared across the domain:
358
- ```typescript
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
- (Do) Colocate `types.ts` inside the domain folder it belongs to:
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
- (Do) Keep single-use types inline in the file that uses them:
384
- ```typescript
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
- (Do) Import shared types from the owning domain's `types.ts`:
395
- ```typescript
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:donts """
401
- (Don't) Use a default export in a types file:
402
- ```typescript
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
- (Don't) Extract single-use types to `types.ts` when they are only used in one file:
412
- ```typescript
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
- (Don't) Place types far from the domain that owns them:
422
- ```typescript
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
- (Don't) Mix types with functions, constants, or other non-type exports:
431
- ```typescript
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
+ ] .