@canonical/code-standards 0.1.0 → 0.1.2

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/docs/code.md CHANGED
@@ -8,7 +8,7 @@ Experimental APIs must be marked with the @experimental JSDoc tag in type defini
8
8
 
9
9
  ### Do
10
10
 
11
- (Do) Mark an experimental interface with a clear @experimental JSDoc tag and description.
11
+ Mark an experimental interface with a clear @experimental JSDoc tag and description.
12
12
  ```typescript
13
13
  /**
14
14
  * Configuration for the data processing pipeline.
@@ -20,7 +20,7 @@ interface PipelineConfig {
20
20
  }
21
21
  ```
22
22
 
23
- (Do) Add an @experimental tag to a specific property with context about its experimental status.
23
+ Add an @experimental tag to a specific property with context about its experimental status.
24
24
  ```typescript
25
25
  interface PipelineConfig {
26
26
  /**
@@ -31,7 +31,7 @@ interface PipelineConfig {
31
31
  }
32
32
  ```
33
33
 
34
- (Do) Describe the experimental status and any future plans for the API.
34
+ Describe the experimental status and any future plans for the API.
35
35
  ```typescript
36
36
  interface CacheConfig {
37
37
  /**
@@ -44,7 +44,7 @@ interface CacheConfig {
44
44
 
45
45
  ### Don't
46
46
 
47
- (Don't) Use the @experimental tag without an explanation.
47
+ Use the @experimental tag without an explanation.
48
48
  ```typescript
49
49
  interface ProcessorConfig {
50
50
  /** @experimental */ // Bad: No context provided.
@@ -52,7 +52,7 @@ interface ProcessorConfig {
52
52
  }
53
53
  ```
54
54
 
55
- (Don't) Use the @experimental tag without describing what is experimental.
55
+ Use the @experimental tag without describing what is experimental.
56
56
  ```typescript
57
57
  /**
58
58
  * @experimental // Bad: No description of what is experimental.
@@ -70,7 +70,7 @@ Domain constants must be collected in a `constants.ts` file using named exports
70
70
 
71
71
  ### Do
72
72
 
73
- (Do) Create a `constants.ts` file with named exports at the domain level:
73
+ Create a `constants.ts` file with named exports at the domain level
74
74
  ```typescript
75
75
  // features/pricing/constants.ts
76
76
  export const DEFAULT_CURRENCY = "USD";
@@ -78,7 +78,7 @@ export const TAX_RATE = 0.21;
78
78
  export const MAX_DISCOUNT_PERCENT = 50;
79
79
  ```
80
80
 
81
- (Do) Colocate `constants.ts` inside the domain folder it belongs to:
81
+ Colocate `constants.ts` inside the domain folder it belongs to
82
82
  ```
83
83
  features/
84
84
  ├── pricing/
@@ -91,13 +91,13 @@ features/
91
91
  │ └── index.ts
92
92
  ```
93
93
 
94
- (Do) Import constants directly from the owning domain's `constants.ts`:
94
+ Import constants directly from the owning domain's `constants.ts`
95
95
  ```typescript
96
96
  // features/pricing/calculatePrice.ts
97
97
  import { TAX_RATE, DEFAULT_CURRENCY } from "./constants.js";
98
98
  ```
99
99
 
100
- (Do) Keep all exports in `constants.ts` as named exports with the same shape (plain values):
100
+ Keep all exports in `constants.ts` as named exports with the same shape (plain values)
101
101
  ```typescript
102
102
  // constants.ts
103
103
  export const RECONNECT_INTERVAL_MS = 5000;
@@ -105,7 +105,7 @@ export const MAX_RETRIES = 3;
105
105
  export const DEFAULT_TIMEOUT_MS = 30_000;
106
106
  ```
107
107
 
108
- (Do) Keep single-use constants inline in the file that uses them:
108
+ Keep single-use constants inline in the file that uses them
109
109
  ```typescript
110
110
  // features/pricing/applyDiscount.ts
111
111
  const MAX_DISCOUNT_PERCENT = 50;
@@ -118,7 +118,7 @@ export default function applyDiscount(price: number, rate: number): number {
118
118
 
119
119
  ### Don't
120
120
 
121
- (Don't) Use a default export in a constants file:
121
+ Use a default export in a constants file
122
122
  ```typescript
123
123
  // Bad: default export forces consumers to name the import
124
124
  export default {
@@ -127,7 +127,7 @@ export default {
127
127
  };
128
128
  ```
129
129
 
130
- (Don't) Scatter constants across unrelated files instead of collecting them in `constants.ts`:
130
+ Scatter constants across unrelated files instead of collecting them in `constants.ts`
131
131
  ```typescript
132
132
  // Bad: constants buried inside implementation files
133
133
  // calculatePrice.ts
@@ -136,7 +136,7 @@ const calculatePrice = (base: number) => base * (1 + TAX_RATE);
136
136
  export default calculatePrice;
137
137
  ```
138
138
 
139
- (Don't) Place constants far from the domain that owns them:
139
+ Place constants far from the domain that owns them
140
140
  ```typescript
141
141
  // Bad: pricing constants in a top-level shared file
142
142
  // src/constants.ts
@@ -145,7 +145,7 @@ export const MAX_RETRIES = 3; // Belongs in network/
145
145
  export const SESSION_TTL_MS = 3600; // Belongs in auth/
146
146
  ```
147
147
 
148
- (Don't) Mix constants with functions, types, or other non-constant exports:
148
+ Mix constants with functions, types, or other non-constant exports
149
149
  ```typescript
150
150
  // Bad: constants.ts should only contain constant values
151
151
  export const MAX_RETRIES = 3;
@@ -155,149 +155,20 @@ export type RetryConfig = { max: number };
155
155
 
156
156
  ---
157
157
 
158
- ## code/export/barrel-public-api
159
-
160
- Barrel files must exist only to define a public API surface. Internal implementation code must import concrete modules directly rather than routing through local barrels. Public barrels may exist at package entry points or at explicitly public subdomain boundaries.
161
-
162
- ### Do
163
-
164
- (Do) Use a barrel for a package entry point or explicit public API:
165
- ```typescript
166
- // src/index.ts
167
- export { Button } from "./components/Button.js";
168
- export type { ButtonProps } from "./components/Button.types.js";
169
- ```
170
-
171
- (Do) Import internal implementation code from the owning file:
172
- ```typescript
173
- // src/build/buildTheme.ts
174
- import { computeDeltas } from "./computeDeltas.js";
175
- import { recoverPrimitiveRef } from "./recoverPrimitiveRef.js";
176
- ```
177
-
178
- (Do) Keep compatibility barrels thin and documented as public facades:
179
- ```typescript
180
- /** Public compatibility surface. */
181
- export { computeDeltas } from "./computeDeltas.js";
182
- export { formatDelta } from "./formatDelta.js";
183
- ```
184
-
185
- ### Don't
186
-
187
- (Don't) Route internal code through a sibling barrel just because it exists:
188
- ```typescript
189
- // Bad: internal implementation depends on a local barrel
190
- import { computeDeltas, recoverPrimitiveRef } from "./index.js";
191
- ```
192
-
193
- (Don't) Hide domain ownership behind convenience barrels:
194
- ```typescript
195
- // Bad: types appear to belong to context.ts instead of their owning domains
196
- import type { Artifact, CSSNode, OverlayToken } from "./context.js";
197
- ```
198
-
199
- (Don't) Create folder barrels for implementation-only directories with no public API contract:
200
- ```typescript
201
- // build/helpers/index.ts
202
- export * from "./parse.js";
203
- export * from "./format.js";
204
- export * from "./walk.js";
205
- ```
206
-
207
- ---
208
-
209
- ## code/export/shape
210
-
211
- Files must use either a single default export or multiple named exports. When using multiple named exports, all exports must have the same type or shape.
212
-
213
- ### Do
214
-
215
- (Do) Use a single default export for files implementing a single component or function:
216
- ```typescript
217
- // ComponentName.tsx
218
- export default ComponentName;
219
- ```
220
-
221
- (Do) Name atomic function files after the function they export:
222
- ```typescript
223
- // assignElement.ts
224
- export default function assignElement(target: Element, source: Partial<Element>) {
225
- // ...
226
- }
227
-
228
- // formatCurrency.ts
229
- export default function formatCurrency(value: number) {
230
- // ...
231
- }
232
- ```
233
-
234
- (Do) Use multiple named exports for files providing a public API or a collection of related items, and ensure all exports have the same type:
235
- ```typescript
236
- // index.ts
237
- export { ComponentA, ComponentB };
238
-
239
- // types.ts
240
- export type TypeA = { ... };
241
- export type TypeB = { ... };
242
-
243
- // Consistent export shape:
244
- export const myFuncA = (value: string) => {};
245
- export const myFuncB = (value: string) => {};
246
- export const myFuncC = (value: string) => {};
247
- ```
248
-
249
- ### Don't
250
-
251
- (Don't) Mix default and unrelated named exports in a way that confuses the file's purpose:
252
- ```typescript
253
- export default ComponentName;
254
- export const helper = () => {};
255
- ```
256
-
257
- (Don't) Name an atomic function file with a name that doesn't match its exported function:
258
- ```typescript
259
- // helpers.ts — wrong: generic name instead of function name
260
- export default function assignElement(target: Element, source: Partial<Element>) {
261
- // ...
262
- }
263
-
264
- // utils.ts — wrong: generic name instead of function name
265
- export default function formatCurrency(value: number) {
266
- // ...
267
- }
268
- ```
269
-
270
- (Don't) Provide multiple unrelated exports from a file meant for a single domain:
271
- ```typescript
272
- export default debounce;
273
- export const throttle = () => {};
274
- export const logger = () => {};
275
- ```
276
-
277
- (Don't) Export objects of different types or shapes from the same file:
278
- ```typescript
279
- export const transformer = (value: string) => {};
280
- export const reducer = (map: string[]) => {};
281
- class ABC {}
282
- export { ABC };
283
- ```
284
-
285
- ---
286
-
287
158
  ## code/function/composition
288
159
 
289
160
  Each function must handle exactly one specific task. Complex operations must be broken down into smaller, single-purpose functions.
290
161
 
291
162
  ### Do
292
163
 
293
- (Do) Define functions with a single, clear responsibility.
164
+ Define functions with a single, clear responsibility.
294
165
  ```typescript
295
166
  const validateEmail = (email: string): boolean => {
296
167
  return /^[^@]+@[^@]+\\.[^@]+$/.test(email);
297
168
  };
298
169
  ```
299
170
 
300
- (Do) Compose single-purpose functions to build complex logic.
171
+ Compose single-purpose functions to build complex logic.
301
172
  ```typescript
302
173
  const validatePassword = (password: string): boolean => {
303
174
  return password.length >= 8;
@@ -310,7 +181,7 @@ const validateForm = (email: string, password:string): boolean => {
310
181
 
311
182
  ### Don't
312
183
 
313
- (Don't) Mix multiple responsibilities in a single function.
184
+ Mix multiple responsibilities in a single function.
314
185
  ```typescript
315
186
  const processUserData = (user: any) => {
316
187
  // Validates user data
@@ -328,7 +199,7 @@ Functions must be defined as close to their broadest point of use as possible. S
328
199
 
329
200
  ### Do
330
201
 
331
- (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.
202
+ 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.
332
203
  ```typescript
333
204
  // utils/math.ts
334
205
  export function calculateSum(a: number, b: number): number {
@@ -339,21 +210,11 @@ export function calculateSum(a: number, b: number): number {
339
210
  import { calculateSum } from './utils/math.js';
340
211
 
341
212
  // For single-use functions, keep them close to their usage:
342
- ```typescript
343
- // services/UserService.ts
344
- const formatUserName = (user: User) => `${user.firstName} ${user.lastName}`;
345
-
346
- const getProfileData = (user: User) => {
347
- const formattedName = formatUserName(user);
348
- return {
349
- ...user,
350
- formattedName,
351
- };
352
- };
213
+ ```
353
214
 
354
215
  ### Don't
355
216
 
356
- (Don't) Place functions far from where they are used, or in a generic location if only used in one place.
217
+ Place functions far from where they are used, or in a generic location if only used in one place.
357
218
  ```typescript
358
219
  // utils/validators.ts
359
220
  export const validateUser = (user: User) => { ... }; // Only used in one component
@@ -362,9 +223,7 @@ export const validateUser = (user: User) => { ... }; // Only used in one compone
362
223
  import { validateUser } from '../utils/validators.js'; // Less ideal if only used here
363
224
 
364
225
  // Don't mix unrelated functions in a single file:
365
- ```typescript
366
- export const mathFunc = () => {};
367
- export const stringFunc = () => {};
226
+ ```
368
227
 
369
228
  ---
370
229
 
@@ -374,14 +233,14 @@ Functions should be pure where possible. A pure function returns the same output
374
233
 
375
234
  ### Do
376
235
 
377
- (Do) Create pure functions that rely only on their inputs to compute the output.
236
+ Create pure functions that rely only on their inputs to compute the output.
378
237
  ```typescript
379
238
  const calculatePrice = (basePrice: number, taxRate: number): number => {
380
239
  return basePrice * (1 + taxRate);
381
240
  };
382
241
  ```
383
242
 
384
- (Do) Annotate and document impure functions that perform I/O or modify external state, explaining why impurity is necessary using @note.
243
+ Annotate and document impure functions that perform I/O or modify external state, explaining why impurity is necessary using @note.
385
244
  ```typescript
386
245
  /**
387
246
  * Writes data to the file system.
@@ -394,7 +253,7 @@ function writeOutputToFile(data: string, path: string) {
394
253
 
395
254
  ### Don't
396
255
 
397
- (Don't) Create impure functions without annotation or documentation.
256
+ Create impure functions without annotation or documentation.
398
257
  ```typescript
399
258
  let total = 0;
400
259
  const addToTotal = (value: number) => {
@@ -402,7 +261,7 @@ const addToTotal = (value: number) => {
402
261
  };
403
262
  ```
404
263
 
405
- (Don't) Hide side effects in functions that appear pure.
264
+ Hide side effects in functions that appear pure.
406
265
  ```typescript
407
266
  function getConfig() {
408
267
  // Reads from disk without annotation
@@ -418,7 +277,7 @@ All function and method names must start with a verb that describes the action t
418
277
 
419
278
  ### Do
420
279
 
421
- (Do) Start function names with an action verb:
280
+ Start function names with an action verb
422
281
  ```typescript
423
282
  const findPathToModule = (name: string): string => { /* ... */ };
424
283
  const fetchDataForUser = (userId: string): Promise<User> => { /* ... */ };
@@ -428,7 +287,7 @@ const isValidEmail = (email: string): boolean => { /* ... */ };
428
287
  const hasPermission = (user: User, action: string): boolean => { /* ... */ };
429
288
  ```
430
289
 
431
- (Do) Use descriptive verb prefixes that convey the operation:
290
+ Use descriptive verb prefixes that convey the operation
432
291
  ```typescript
433
292
  // Retrieval: get, fetch, find, resolve, load, read
434
293
  const getUser = (id: string): User => { /* ... */ };
@@ -455,7 +314,7 @@ const buildQuery = (params: Params): string => { /* ... */ };
455
314
 
456
315
  ### Don't
457
316
 
458
- (Don't) Name functions as nouns or noun phrases — they read like values, not actions:
317
+ Name functions as nouns or noun phrases — they read like values, not actions
459
318
  ```typescript
460
319
  // Bad: reads like a variable, not a function
461
320
  const pathToModule = (name: string): string => { /* ... */ };
@@ -468,7 +327,7 @@ const userPermissions = (user: User): Permission[] => { /* ... */ };
468
327
  // Good: getPermissions or listPermissions
469
328
  ```
470
329
 
471
- (Don't) Use vague or non-descriptive verbs that don't convey meaning:
330
+ Use vague or non-descriptive verbs that don't convey meaning
472
331
  ```typescript
473
332
  // Bad: "do" and "process" are too vague on their own
474
333
  const doEmail = (email: string) => { /* ... */ };
@@ -480,162 +339,13 @@ const processUser = (user: User) => { /* ... */ };
480
339
 
481
340
  ---
482
341
 
483
- ## code/naming/single-export-file
484
-
485
- Files that contain a single export must be named after that export. This applies to functions, classes, types, constants, and any other single-export module. The file name must match the exported identifier exactly, preserving its casing (camelCase for functions/variables, PascalCase for classes/types/components).
486
-
487
- ### Do
488
-
489
- (Do) Name files after the single function they export:
490
- ```typescript
491
- // isAccepted.ts
492
- export const isAccepted = (status: string): boolean => {
493
- return status === "accepted";
494
- };
495
-
496
- // formatCurrency.ts
497
- export default function formatCurrency(value: number): string {
498
- return `$${value.toFixed(2)}`;
499
- }
500
- ```
501
-
502
- (Do) Name files after the single type or interface they export:
503
- ```typescript
504
- // ConnectionConfig.ts
505
- export interface ConnectionConfig {
506
- host: string;
507
- port: number;
508
- }
509
-
510
- // UserRole.ts
511
- export type UserRole = "admin" | "editor" | "viewer";
512
- ```
513
-
514
- (Do) Name files after the single class they export:
515
- ```typescript
516
- // EventEmitter.ts
517
- export class EventEmitter {
518
- // ...
519
- }
520
- ```
521
-
522
- (Do) Name files after the single constant they export:
523
- ```typescript
524
- // DEFAULT_TIMEOUT.ts
525
- export const DEFAULT_TIMEOUT = 5000;
526
- ```
527
-
528
- ### Don't
529
-
530
- (Don't) Use generic names like `helpers.ts`, `utils.ts`, or `types.ts` for files with a single export:
531
- ```typescript
532
- // helpers.ts — wrong: should be isAccepted.ts
533
- export const isAccepted = (status: string): boolean => {
534
- return status === "accepted";
535
- };
536
-
537
- // utils.ts — wrong: should be formatCurrency.ts
538
- export default function formatCurrency(value: number): string {
539
- return `$${value.toFixed(2)}`;
540
- }
541
- ```
542
-
543
- (Don't) Use names that describe the domain instead of the export:
544
- ```typescript
545
- // validation.ts — wrong: should be isAccepted.ts
546
- export const isAccepted = (status: string): boolean => {
547
- return status === "accepted";
548
- };
549
-
550
- // network.ts — wrong: should be ConnectionConfig.ts
551
- export interface ConnectionConfig {
552
- host: string;
553
- port: number;
554
- }
555
- ```
556
-
557
- ---
558
-
559
- ## code/package/lib-folder
560
-
561
- Packages that export reusable logic (components, utilities, hooks, types) must organize their exportable code in a `lib` folder within the `src` directory. This convention ensures consistency across the monorepo and clearly identifies code that is part of the package's public API.
562
-
563
- ### Do
564
-
565
- (Do) Place all reusable/exportable code in `src/lib/`:
566
- ```
567
- packages/my-package/
568
- ├── src/
569
- │ ├── lib/
570
- │ │ ├── Button/
571
- │ │ │ ├── Button.tsx
572
- │ │ │ ├── Button.tests.tsx
573
- │ │ │ ├── types.ts
574
- │ │ │ └── index.ts
575
- │ │ ├── hooks/
576
- │ │ │ └── useToggle.ts
577
- │ │ ├── types/
578
- │ │ │ └── index.ts
579
- │ │ └── index.ts
580
- │ ├── storybook/ # Storybook-specific files (not exported)
581
- │ └── index.ts # Re-exports from lib
582
- └── package.json
583
- ```
584
-
585
- (Do) Re-export from the lib folder in the package entry point:
586
- ```typescript
587
- // src/index.ts
588
- export * from "./lib/index.js";
589
- ```
590
-
591
- (Do) Use the lib folder for components, hooks, utilities, types, and any other code meant to be consumed by package users:
592
- ```typescript
593
- // src/lib/index.ts
594
- export * from "./Button/index.js";
595
- export * from "./hooks/index.js";
596
- export type * from "./types/index.js";
597
- ```
598
-
599
- ### Don't
600
-
601
- (Don't) Use alternative folder names like `ui`, `components`, or `utils` for exportable code at the package level:
602
- ```
603
- // Bad: Using 'ui' instead of 'lib'
604
- packages/my-package/
605
- ├── src/
606
- │ ├── ui/ # Wrong: should be 'lib'
607
- │ │ └── Button/
608
- │ └── index.ts
609
- ```
610
-
611
- (Don't) Mix exportable and non-exportable code at the same level:
612
- ```
613
- // Bad: Mixing concerns
614
- packages/my-package/
615
- ├── src/
616
- │ ├── Button/ # Component mixed with...
617
- │ ├── storybook/ # ...non-exportable storybook config
618
- │ └── test-utils/ # ...and test utilities
619
- ```
620
-
621
- (Don't) Create deeply nested lib-like structures; keep lib at the top level of src:
622
- ```
623
- // Bad: Nested lib folders
624
- packages/my-package/
625
- ├── src/
626
- │ ├── features/
627
- │ │ └── lib/ # Wrong: lib should be at src level
628
- ```
629
-
630
- ---
631
-
632
342
  ## code/types/file
633
343
 
634
344
  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`.
635
345
 
636
346
  ### Do
637
347
 
638
- (Do) Create a `types.ts` file with named exports for types shared across the domain:
348
+ Create a `types.ts` file with named exports for types shared across the domain
639
349
  ```typescript
640
350
  // features/pricing/types.ts
641
351
  export type Currency = "USD" | "EUR" | "GBP";
@@ -647,7 +357,7 @@ export interface PriceBreakdown {
647
357
  export type DiscountStrategy = "percentage" | "fixed";
648
358
  ```
649
359
 
650
- (Do) Colocate `types.ts` inside the domain folder it belongs to:
360
+ Colocate `types.ts` inside the domain folder it belongs to
651
361
  ```
652
362
  features/
653
363
  ├── pricing/
@@ -661,7 +371,7 @@ features/
661
371
  │ └── index.ts
662
372
  ```
663
373
 
664
- (Do) Keep single-use types inline in the file that uses them:
374
+ Keep single-use types inline in the file that uses them
665
375
  ```typescript
666
376
  // features/pricing/applyDiscount.ts
667
377
  type DiscountResult = { discounted: number; savings: number };
@@ -672,7 +382,7 @@ export default function applyDiscount(price: number, rate: number): DiscountResu
672
382
  }
673
383
  ```
674
384
 
675
- (Do) Import shared types from the owning domain's `types.ts`:
385
+ Import shared types from the owning domain's `types.ts`
676
386
  ```typescript
677
387
  // features/pricing/calculatePrice.ts
678
388
  import type { Currency, PriceBreakdown } from "./types.js";
@@ -680,7 +390,7 @@ import type { Currency, PriceBreakdown } from "./types.js";
680
390
 
681
391
  ### Don't
682
392
 
683
- (Don't) Use a default export in a types file:
393
+ Use a default export in a types file
684
394
  ```typescript
685
395
  // Bad: default export for a collection of types
686
396
  export default interface PriceBreakdown {
@@ -690,7 +400,7 @@ export default interface PriceBreakdown {
690
400
  }
691
401
  ```
692
402
 
693
- (Don't) Extract single-use types to `types.ts` when they are only used in one file:
403
+ Extract single-use types to `types.ts` when they are only used in one file
694
404
  ```typescript
695
405
  // Bad: types.ts contains a type only used in applyDiscount.ts
696
406
  // types.ts
@@ -700,7 +410,7 @@ export type DiscountResult = { discounted: number; savings: number };
700
410
  import type { DiscountResult } from "./types.js"; // Unnecessary indirection
701
411
  ```
702
412
 
703
- (Don't) Place types far from the domain that owns them:
413
+ Place types far from the domain that owns them
704
414
  ```typescript
705
415
  // Bad: all types dumped in a top-level shared file
706
416
  // src/types.ts
@@ -709,7 +419,7 @@ export type AuthToken = { jwt: string }; // Belongs in auth/
709
419
  export type RetryPolicy = { max: number }; // Belongs in network/
710
420
  ```
711
421
 
712
- (Don't) Mix types with functions, constants, or other non-type exports:
422
+ Mix types with functions, constants, or other non-type exports
713
423
  ```typescript
714
424
  // Bad: types.ts should only contain type declarations
715
425
  export type Currency = "USD" | "EUR" | "GBP";