@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/.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 +24 -20
- package/docs/icons.md +41 -42
- package/docs/index.md +2 -1
- package/docs/packaging.md +643 -0
- package/docs/react.md +58 -59
- 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/packaging.ttl
CHANGED
|
@@ -15,18 +15,20 @@ cs:ApplicationPackage a cs:CodeStandard ;
|
|
|
15
15
|
cs:name "packaging/application/structure" ;
|
|
16
16
|
cs:hasCategory cs:PackagingCategory ;
|
|
17
17
|
cs:description "Application packages (CLIs, servers, MCP tools, build scripts, workers) execute behavior through entry points. They are not imported as dependencies by other packages. Their internal code must be organized around execution domains (commands, routes, tools, operations) rather than library conventions like `src/lib/` or barrel re-exports. Each domain folder must have its own barrel file (`index.ts`) that defines the domain's internal API — sibling domains import through the barrel, never from individual files." ;
|
|
18
|
-
cs:
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
cs:do [
|
|
19
|
+
cs:description "Recognise an application package by its `package.json` signals — `bin` field, `private: true`, no `main`/`exports`" ;
|
|
20
|
+
cs:language "json" ;
|
|
21
|
+
cs:code """
|
|
21
22
|
{
|
|
22
23
|
"name": "@scope/my-cli",
|
|
23
24
|
"private": true,
|
|
24
25
|
"bin": { "my-cli": "./dist/index.js" }
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
"""
|
|
28
|
+
] ;
|
|
29
|
+
cs:do [
|
|
30
|
+
cs:description "Organize code around execution domains. Each domain folder gets its own barrel (`index.ts`) that exposes the domain's internal API to sibling domains" ;
|
|
31
|
+
cs:code """
|
|
30
32
|
packages/my-cli/
|
|
31
33
|
├── src/
|
|
32
34
|
│ ├── commands/ # CLI command handlers
|
|
@@ -42,17 +44,20 @@ packages/my-cli/
|
|
|
42
44
|
│ │ └── index.ts # Barrel: exports utilities
|
|
43
45
|
│ └── index.ts # Entry point (bootstraps CLI)
|
|
44
46
|
└── package.json
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
"""
|
|
48
|
+
] ;
|
|
49
|
+
cs:do [
|
|
50
|
+
cs:description "Import from sibling domains through their barrel, not from individual files" ;
|
|
51
|
+
cs:language "typescript" ;
|
|
52
|
+
cs:code """
|
|
49
53
|
// src/commands/deploy.ts
|
|
50
54
|
import { resolveConfig, runValidation } from "../operations/index.js";
|
|
51
55
|
import { formatOutput } from "../utils/index.js";
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
"""
|
|
57
|
+
] ;
|
|
58
|
+
cs:do [
|
|
59
|
+
cs:description "Use domain-appropriate folder names that reflect what the code does — `commands/`, `tools/`, `routes/`, `operations/`, `handlers/`" ;
|
|
60
|
+
cs:code """
|
|
56
61
|
packages/my-mcp/
|
|
57
62
|
├── src/
|
|
58
63
|
│ ├── tools/ # MCP tool handlers
|
|
@@ -64,10 +69,11 @@ packages/my-mcp/
|
|
|
64
69
|
│ │ └── index.ts # Barrel: exports shared operations
|
|
65
70
|
│ └── index.ts # MCP server entry point
|
|
66
71
|
└── package.json
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
"""
|
|
73
|
+
] ;
|
|
74
|
+
cs:do [
|
|
75
|
+
cs:description "For **hybrid** packages (primarily application but exporting a small reusable surface), isolate the exported surface in `src/lib/` following library rules while keeping the rest as application structure" ;
|
|
76
|
+
cs:code """
|
|
71
77
|
packages/my-mcp/
|
|
72
78
|
├── src/
|
|
73
79
|
│ ├── lib/ # Only the reusable exported surface
|
|
@@ -82,11 +88,11 @@ packages/my-mcp/
|
|
|
82
88
|
│ │ └── index.ts # Barrel: exports shared operations
|
|
83
89
|
│ └── index.ts # MCP server entry point
|
|
84
90
|
└── package.json
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
cs:
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
"""
|
|
92
|
+
] ;
|
|
93
|
+
cs:dont [
|
|
94
|
+
cs:description "Apply library structure (`src/lib/`, barrel re-exports) to application packages" ;
|
|
95
|
+
cs:code """
|
|
90
96
|
// Bad: CLI tool forced into library layout
|
|
91
97
|
packages/my-cli/
|
|
92
98
|
├── src/
|
|
@@ -94,10 +100,11 @@ packages/my-cli/
|
|
|
94
100
|
│ │ ├── commands/
|
|
95
101
|
│ │ └── index.ts # Barrel re-export — nobody imports this
|
|
96
102
|
│ └── index.ts
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
"""
|
|
104
|
+
] ;
|
|
105
|
+
cs:dont [
|
|
106
|
+
cs:description "Create a `src/lib/` folder for internal shared operations. Use domain-appropriate names instead" ;
|
|
107
|
+
cs:code """
|
|
101
108
|
// Bad: "lib" implies external consumption
|
|
102
109
|
packages/my-cli/
|
|
103
110
|
├── src/
|
|
@@ -109,10 +116,12 @@ packages/my-cli/
|
|
|
109
116
|
├── src/
|
|
110
117
|
│ ├── operations/
|
|
111
118
|
│ │ └── resolveConfig.ts # Internal shared logic
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
"""
|
|
120
|
+
] ;
|
|
121
|
+
cs:dont [
|
|
122
|
+
cs:description "Import directly from files inside a sibling domain — always go through the domain barrel" ;
|
|
123
|
+
cs:language "typescript" ;
|
|
124
|
+
cs:code """
|
|
116
125
|
// Bad: reaching into a sibling domain's internals
|
|
117
126
|
import { resolveConfig } from "../operations/resolveConfig.js";
|
|
118
127
|
import { formatOutput } from "../utils/formatOutput.js";
|
|
@@ -120,10 +129,11 @@ import { formatOutput } from "../utils/formatOutput.js";
|
|
|
120
129
|
// Good: import through the domain barrel
|
|
121
130
|
import { resolveConfig } from "../operations/index.js";
|
|
122
131
|
import { formatOutput } from "../utils/index.js";
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
"""
|
|
133
|
+
] ;
|
|
134
|
+
cs:dont [
|
|
135
|
+
cs:description "Omit barrels from domain folders" ;
|
|
136
|
+
cs:code """
|
|
127
137
|
// Bad: no barrels — every consumer imports individual files
|
|
128
138
|
packages/my-cli/
|
|
129
139
|
├── src/
|
|
@@ -133,36 +143,39 @@ packages/my-cli/
|
|
|
133
143
|
│ ├── operations/
|
|
134
144
|
│ │ ├── resolveConfig.ts
|
|
135
145
|
│ │ └── runValidation.ts # No index.ts — fragile cross-domain imports
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
146
|
+
"""
|
|
147
|
+
] ;
|
|
148
|
+
cs:dont [
|
|
149
|
+
cs:description "Default to library structure just because the package lives in a monorepo" ;
|
|
150
|
+
cs:code """
|
|
140
151
|
// Bad: reflexively adding lib/ to a build script package
|
|
141
152
|
packages/build-tools/
|
|
142
153
|
├── src/
|
|
143
154
|
│ ├── lib/ # This is a build script, not a library
|
|
144
155
|
│ │ └── runBuild.ts
|
|
145
156
|
│ └── index.ts
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
"""
|
|
158
|
+
] .
|
|
148
159
|
|
|
149
160
|
# Library Package Structure
|
|
150
161
|
cs:LibraryPackage a cs:CodeStandard ;
|
|
151
162
|
cs:name "packaging/library/structure" ;
|
|
152
163
|
cs:hasCategory cs:PackagingCategory ;
|
|
153
164
|
cs:description "Library packages export reusable code (components, utilities, hooks, types) for consumption by other packages. They must organize their exportable code in a `src/lib/` folder and re-export through a root barrel. This convention ensures consistency across the monorepo and clearly separates public API code from non-exported concerns like storybook configuration or test utilities. Each domain folder within `lib/` must have its own barrel (`index.ts`)." ;
|
|
154
|
-
cs:
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
cs:do [
|
|
166
|
+
cs:description "Recognise a library package by its `package.json` signals — `main`/`exports` field, publishable, no `bin`" ;
|
|
167
|
+
cs:language "json" ;
|
|
168
|
+
cs:code """
|
|
157
169
|
{
|
|
158
170
|
"name": "@scope/design-system",
|
|
159
171
|
"main": "./dist/index.js",
|
|
160
172
|
"exports": { ".": "./dist/index.js" }
|
|
161
173
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
"""
|
|
175
|
+
] ;
|
|
176
|
+
cs:do [
|
|
177
|
+
cs:description "Place all reusable/exportable code in `src/lib/`, with a barrel in each domain folder" ;
|
|
178
|
+
cs:code """
|
|
166
179
|
packages/my-package/
|
|
167
180
|
├── src/
|
|
168
181
|
│ ├── lib/
|
|
@@ -180,54 +193,61 @@ packages/my-package/
|
|
|
180
193
|
│ ├── storybook/ # Storybook-specific files (not exported)
|
|
181
194
|
│ └── index.ts # Re-exports from lib
|
|
182
195
|
└── package.json
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
196
|
+
"""
|
|
197
|
+
] ;
|
|
198
|
+
cs:do [
|
|
199
|
+
cs:description "Re-export from the lib folder in the package entry point" ;
|
|
200
|
+
cs:language "typescript" ;
|
|
201
|
+
cs:code """
|
|
187
202
|
// src/index.ts
|
|
188
203
|
export * from "./lib/index.js";
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
"""
|
|
205
|
+
] ;
|
|
206
|
+
cs:do [
|
|
207
|
+
cs:description "Use the lib barrel to compose the package's public API from domain barrels" ;
|
|
208
|
+
cs:language "typescript" ;
|
|
209
|
+
cs:code """
|
|
193
210
|
// src/lib/index.ts
|
|
194
211
|
export * from "./Button/index.js";
|
|
195
212
|
export * from "./hooks/index.js";
|
|
196
213
|
export type * from "./types/index.js";
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
cs:
|
|
200
|
-
|
|
201
|
-
|
|
214
|
+
"""
|
|
215
|
+
] ;
|
|
216
|
+
cs:dont [
|
|
217
|
+
cs:description "Use alternative folder names like `ui`, `components`, or `utils` for exportable code at the package level" ;
|
|
218
|
+
cs:code """
|
|
202
219
|
// Bad: Using 'ui' instead of 'lib'
|
|
203
220
|
packages/my-package/
|
|
204
221
|
├── src/
|
|
205
222
|
│ ├── ui/ # Wrong: should be 'lib'
|
|
206
223
|
│ │ └── Button/
|
|
207
224
|
│ └── index.ts
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
225
|
+
"""
|
|
226
|
+
] ;
|
|
227
|
+
cs:dont [
|
|
228
|
+
cs:description "Mix exportable and non-exportable code at the same level" ;
|
|
229
|
+
cs:code """
|
|
212
230
|
// Bad: Mixing concerns
|
|
213
231
|
packages/my-package/
|
|
214
232
|
├── src/
|
|
215
233
|
│ ├── Button/ # Component mixed with...
|
|
216
234
|
│ ├── storybook/ # ...non-exportable storybook config
|
|
217
235
|
│ └── test-utils/ # ...and test utilities
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
236
|
+
"""
|
|
237
|
+
] ;
|
|
238
|
+
cs:dont [
|
|
239
|
+
cs:description "Create deeply nested lib-like structures; keep lib at the top level of src" ;
|
|
240
|
+
cs:code """
|
|
222
241
|
// Bad: Nested lib folders
|
|
223
242
|
packages/my-package/
|
|
224
243
|
├── src/
|
|
225
244
|
│ ├── features/
|
|
226
245
|
│ │ └── lib/ # Wrong: lib should be at src level
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
246
|
+
"""
|
|
247
|
+
] ;
|
|
248
|
+
cs:dont [
|
|
249
|
+
cs:description "Omit barrels from domain folders within lib" ;
|
|
250
|
+
cs:code """
|
|
231
251
|
// Bad: no barrels — consumers must know individual file paths
|
|
232
252
|
packages/my-package/
|
|
233
253
|
├── src/
|
|
@@ -238,23 +258,26 @@ packages/my-package/
|
|
|
238
258
|
│ │ ├── hooks/
|
|
239
259
|
│ │ │ └── useToggle.ts # No index.ts
|
|
240
260
|
│ │ └── index.ts
|
|
241
|
-
|
|
242
|
-
|
|
261
|
+
"""
|
|
262
|
+
] .
|
|
243
263
|
|
|
244
264
|
# Export Shape Standard
|
|
245
265
|
cs:ExportShape a cs:CodeStandard ;
|
|
246
266
|
cs:name "packaging/export/shape" ;
|
|
247
267
|
cs:hasCategory cs:PackagingCategory ;
|
|
248
268
|
cs:description "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." ;
|
|
249
|
-
cs:
|
|
250
|
-
|
|
251
|
-
|
|
269
|
+
cs:do [
|
|
270
|
+
cs:description "Use a single default export for files implementing a single component or function" ;
|
|
271
|
+
cs:language "typescript" ;
|
|
272
|
+
cs:code """
|
|
252
273
|
// ComponentName.tsx
|
|
253
274
|
export default ComponentName;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
275
|
+
"""
|
|
276
|
+
] ;
|
|
277
|
+
cs:do [
|
|
278
|
+
cs:description "Name atomic function files after the function they export" ;
|
|
279
|
+
cs:language "typescript" ;
|
|
280
|
+
cs:code """
|
|
258
281
|
// assignElement.ts
|
|
259
282
|
export default function assignElement(target: Element, source: Partial<Element>) {
|
|
260
283
|
// ...
|
|
@@ -264,10 +287,12 @@ export default function assignElement(target: Element, source: Partial<Element>)
|
|
|
264
287
|
export default function formatCurrency(value: number) {
|
|
265
288
|
// ...
|
|
266
289
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
290
|
+
"""
|
|
291
|
+
] ;
|
|
292
|
+
cs:do [
|
|
293
|
+
cs:description "Use multiple named exports for files providing a public API or a collection of related items, and ensure all exports have the same type" ;
|
|
294
|
+
cs:language "typescript" ;
|
|
295
|
+
cs:code """
|
|
271
296
|
// index.ts
|
|
272
297
|
export { ComponentA, ComponentB };
|
|
273
298
|
|
|
@@ -279,17 +304,20 @@ export type TypeB = { ... };
|
|
|
279
304
|
export const myFuncA = (value: string) => {};
|
|
280
305
|
export const myFuncB = (value: string) => {};
|
|
281
306
|
export const myFuncC = (value: string) => {};
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
cs:
|
|
285
|
-
|
|
286
|
-
|
|
307
|
+
"""
|
|
308
|
+
] ;
|
|
309
|
+
cs:dont [
|
|
310
|
+
cs:description "Mix default and unrelated named exports in a way that confuses the file's purpose" ;
|
|
311
|
+
cs:language "typescript" ;
|
|
312
|
+
cs:code """
|
|
287
313
|
export default ComponentName;
|
|
288
314
|
export const helper = () => {};
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
315
|
+
"""
|
|
316
|
+
] ;
|
|
317
|
+
cs:dont [
|
|
318
|
+
cs:description "Name an atomic function file with a name that doesn't match its exported function" ;
|
|
319
|
+
cs:language "typescript" ;
|
|
320
|
+
cs:code """
|
|
293
321
|
// helpers.ts — wrong: generic name instead of function name
|
|
294
322
|
export default function assignElement(target: Element, source: Partial<Element>) {
|
|
295
323
|
// ...
|
|
@@ -299,104 +327,209 @@ export default function assignElement(target: Element, source: Partial<Element>)
|
|
|
299
327
|
export default function formatCurrency(value: number) {
|
|
300
328
|
// ...
|
|
301
329
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
330
|
+
"""
|
|
331
|
+
] ;
|
|
332
|
+
cs:dont [
|
|
333
|
+
cs:description "Provide multiple unrelated exports from a file meant for a single domain" ;
|
|
334
|
+
cs:language "typescript" ;
|
|
335
|
+
cs:code """
|
|
306
336
|
export default debounce;
|
|
307
337
|
export const throttle = () => {};
|
|
308
338
|
export const logger = () => {};
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
339
|
+
"""
|
|
340
|
+
] ;
|
|
341
|
+
cs:dont [
|
|
342
|
+
cs:description "Export objects of different types or shapes from the same file" ;
|
|
343
|
+
cs:language "typescript" ;
|
|
344
|
+
cs:code """
|
|
313
345
|
export const transformer = (value: string) => {};
|
|
314
346
|
export const reducer = (map: string[]) => {};
|
|
315
347
|
class ABC {}
|
|
316
348
|
export { ABC };
|
|
317
|
-
|
|
318
|
-
|
|
349
|
+
"""
|
|
350
|
+
] .
|
|
319
351
|
|
|
320
352
|
# Barrel File Standard
|
|
321
353
|
cs:BarrelPublicApi a cs:CodeStandard ;
|
|
322
354
|
cs:name "packaging/export/barrel" ;
|
|
323
355
|
cs:hasCategory cs:PackagingCategory ;
|
|
324
356
|
cs:description "Every domain folder must have a barrel file (`index.ts`) that defines its API surface. In library packages, barrels define the public API for external consumers. In application packages, barrels define the internal API between sibling domains. Within a domain, implementation files import from siblings directly; cross-domain imports always go through the barrel." ;
|
|
325
|
-
cs:
|
|
326
|
-
|
|
327
|
-
|
|
357
|
+
cs:do [
|
|
358
|
+
cs:description "Use a barrel at the package entry point to define the public API (library packages)" ;
|
|
359
|
+
cs:language "typescript" ;
|
|
360
|
+
cs:code """
|
|
328
361
|
// src/index.ts
|
|
329
362
|
export { Button } from "./components/Button.js";
|
|
330
363
|
export type { ButtonProps } from "./components/Button.types.js";
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
364
|
+
"""
|
|
365
|
+
] ;
|
|
366
|
+
cs:do [
|
|
367
|
+
cs:description "Use a barrel at each domain folder to define the domain's API (application packages)" ;
|
|
368
|
+
cs:language "typescript" ;
|
|
369
|
+
cs:code """
|
|
335
370
|
// src/operations/index.ts
|
|
336
371
|
export { resolveConfig } from "./resolveConfig.js";
|
|
337
372
|
export { runValidation } from "./runValidation.js";
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
373
|
+
"""
|
|
374
|
+
] ;
|
|
375
|
+
cs:do [
|
|
376
|
+
cs:description "Import internal implementation code within a domain from the owning file directly" ;
|
|
377
|
+
cs:language "typescript" ;
|
|
378
|
+
cs:code """
|
|
342
379
|
// src/build/buildTheme.ts — same domain, import the file
|
|
343
380
|
import { computeDeltas } from "./computeDeltas.js";
|
|
344
381
|
import { recoverPrimitiveRef } from "./recoverPrimitiveRef.js";
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
382
|
+
"""
|
|
383
|
+
] ;
|
|
384
|
+
cs:do [
|
|
385
|
+
cs:description "Import cross-domain code through the sibling domain's barrel" ;
|
|
386
|
+
cs:language "typescript" ;
|
|
387
|
+
cs:code """
|
|
349
388
|
// src/commands/deploy.ts — different domain, import through barrel
|
|
350
389
|
import { resolveConfig } from "../operations/index.js";
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
390
|
+
"""
|
|
391
|
+
] ;
|
|
392
|
+
cs:do [
|
|
393
|
+
cs:description "Keep compatibility barrels thin and documented as public facades" ;
|
|
394
|
+
cs:language "typescript" ;
|
|
395
|
+
cs:code """
|
|
355
396
|
/** Public compatibility surface. */
|
|
356
397
|
export { computeDeltas } from "./computeDeltas.js";
|
|
357
398
|
export { formatDelta } from "./formatDelta.js";
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
cs:
|
|
361
|
-
|
|
362
|
-
|
|
399
|
+
"""
|
|
400
|
+
] ;
|
|
401
|
+
cs:dont [
|
|
402
|
+
cs:description "Route internal same-domain code through its own barrel" ;
|
|
403
|
+
cs:language "typescript" ;
|
|
404
|
+
cs:code """
|
|
363
405
|
// Bad: internal implementation depends on its own barrel
|
|
364
406
|
// src/build/applyTheme.ts
|
|
365
407
|
import { computeDeltas, recoverPrimitiveRef } from "./index.js";
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
408
|
+
"""
|
|
409
|
+
] ;
|
|
410
|
+
cs:dont [
|
|
411
|
+
cs:description "Import cross-domain code by reaching into individual files" ;
|
|
412
|
+
cs:language "typescript" ;
|
|
413
|
+
cs:code """
|
|
370
414
|
// Bad: bypassing the domain barrel
|
|
371
415
|
import { resolveConfig } from "../operations/resolveConfig.js";
|
|
372
416
|
|
|
373
417
|
// Good: through the barrel
|
|
374
418
|
import { resolveConfig } from "../operations/index.js";
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
419
|
+
"""
|
|
420
|
+
] ;
|
|
421
|
+
cs:dont [
|
|
422
|
+
cs:description "Hide domain ownership behind convenience barrels" ;
|
|
423
|
+
cs:language "typescript" ;
|
|
424
|
+
cs:code """
|
|
379
425
|
// Bad: types appear to belong to context.ts instead of their owning domains
|
|
380
426
|
import type { Artifact, CSSNode, OverlayToken } from "./context.js";
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
427
|
+
"""
|
|
428
|
+
] ;
|
|
429
|
+
cs:dont [
|
|
430
|
+
cs:description "Omit the barrel from a domain folder" ;
|
|
431
|
+
cs:code """
|
|
385
432
|
// Bad: no index.ts — consumers must know internal file names
|
|
386
433
|
src/operations/
|
|
387
434
|
├── resolveConfig.ts
|
|
388
435
|
└── runValidation.ts
|
|
389
|
-
|
|
390
|
-
|
|
436
|
+
"""
|
|
437
|
+
] .
|
|
438
|
+
|
|
439
|
+
# Export Declaration Location
|
|
440
|
+
cs:ExportDeclarationLocation a cs:CodeStandard ;
|
|
441
|
+
cs:name "packaging/export/declaration" ;
|
|
442
|
+
cs:hasCategory cs:PackagingCategory ;
|
|
443
|
+
cs:description "Exports must be declared on the definition itself (e.g. `export default function` or `export const`), not gathered at the end of the file. When a wrapper (HOC, decorator, middleware) is applied, declare the unwrapped binding with `const`, then export the wrapped result as default on a separate line." ;
|
|
444
|
+
cs:do [
|
|
445
|
+
cs:description "Declare the export directly on the function or class definition" ;
|
|
446
|
+
cs:language "typescript" ;
|
|
447
|
+
cs:code """
|
|
448
|
+
// formatCurrency.ts
|
|
449
|
+
export default function formatCurrency(value: number): string {
|
|
450
|
+
return `$${value.toFixed(2)}`;
|
|
451
|
+
}
|
|
452
|
+
"""
|
|
453
|
+
] ;
|
|
454
|
+
cs:do [
|
|
455
|
+
cs:description "Declare the export directly on a named constant" ;
|
|
456
|
+
cs:language "typescript" ;
|
|
457
|
+
cs:code """
|
|
458
|
+
// MAX_RETRIES.ts
|
|
459
|
+
export const MAX_RETRIES = 3;
|
|
460
|
+
"""
|
|
461
|
+
] ;
|
|
462
|
+
cs:do [
|
|
463
|
+
cs:description "When a wrapper is involved, declare the raw binding with `const`, then export the wrapped result as default" ;
|
|
464
|
+
cs:language "typescript" ;
|
|
465
|
+
cs:code """
|
|
466
|
+
// UserProfile.tsx
|
|
467
|
+
const UserProfile = ({ user }: UserProfileProps) => {
|
|
468
|
+
return <div>{user.name}</div>;
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
export default memo(UserProfile);
|
|
472
|
+
"""
|
|
473
|
+
] ;
|
|
474
|
+
cs:do [
|
|
475
|
+
cs:description "Apply the same pattern for higher-order functions and middleware" ;
|
|
476
|
+
cs:language "typescript" ;
|
|
477
|
+
cs:code """
|
|
478
|
+
// connectDatabase.ts
|
|
479
|
+
const connectDatabase = (config: DbConfig): Connection => {
|
|
480
|
+
return new Connection(config);
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
export default withRetry(connectDatabase);
|
|
484
|
+
"""
|
|
485
|
+
] ;
|
|
486
|
+
cs:dont [
|
|
487
|
+
cs:description "Define a symbol and then export it at the end of the file" ;
|
|
488
|
+
cs:language "typescript" ;
|
|
489
|
+
cs:code """
|
|
490
|
+
// Bad: export is separated from definition
|
|
491
|
+
function formatCurrency(value: number): string {
|
|
492
|
+
return `$${value.toFixed(2)}`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export default formatCurrency;
|
|
496
|
+
"""
|
|
497
|
+
] ;
|
|
498
|
+
cs:dont [
|
|
499
|
+
cs:description "Gather multiple exports at the bottom of the file" ;
|
|
500
|
+
cs:language "typescript" ;
|
|
501
|
+
cs:code """
|
|
502
|
+
// Bad: exports collected at the end
|
|
503
|
+
const helperA = () => {};
|
|
504
|
+
const helperB = () => {};
|
|
505
|
+
|
|
506
|
+
export { helperA, helperB };
|
|
507
|
+
"""
|
|
508
|
+
] ;
|
|
509
|
+
cs:dont [
|
|
510
|
+
cs:description "Use `export default` on the raw binding when a wrapper is needed" ;
|
|
511
|
+
cs:language "typescript" ;
|
|
512
|
+
cs:code """
|
|
513
|
+
// Bad: exports the unwrapped version, then wraps elsewhere
|
|
514
|
+
export default function UserProfile({ user }: UserProfileProps) {
|
|
515
|
+
return <div>{user.name}</div>;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// consumer.ts — has to wrap it themselves
|
|
519
|
+
import UserProfile from "./UserProfile.js";
|
|
520
|
+
const Memoized = memo(UserProfile);
|
|
521
|
+
"""
|
|
522
|
+
] .
|
|
391
523
|
|
|
392
524
|
# Single Export File Naming Standard
|
|
393
525
|
cs:SingleExportFileNaming a cs:CodeStandard ;
|
|
394
526
|
cs:name "packaging/naming/single-export-file" ;
|
|
395
527
|
cs:hasCategory cs:PackagingCategory ;
|
|
396
528
|
cs:description "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)." ;
|
|
397
|
-
cs:
|
|
398
|
-
|
|
399
|
-
|
|
529
|
+
cs:do [
|
|
530
|
+
cs:description "Name files after the single function they export" ;
|
|
531
|
+
cs:language "typescript" ;
|
|
532
|
+
cs:code """
|
|
400
533
|
// isAccepted.ts
|
|
401
534
|
export const isAccepted = (status: string): boolean => {
|
|
402
535
|
return status === "accepted";
|
|
@@ -406,10 +539,12 @@ export const isAccepted = (status: string): boolean => {
|
|
|
406
539
|
export default function formatCurrency(value: number): string {
|
|
407
540
|
return `$${value.toFixed(2)}`;
|
|
408
541
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
542
|
+
"""
|
|
543
|
+
] ;
|
|
544
|
+
cs:do [
|
|
545
|
+
cs:description "Name files after the single type or interface they export" ;
|
|
546
|
+
cs:language "typescript" ;
|
|
547
|
+
cs:code """
|
|
413
548
|
// ConnectionConfig.ts
|
|
414
549
|
export interface ConnectionConfig {
|
|
415
550
|
host: string;
|
|
@@ -418,25 +553,30 @@ export interface ConnectionConfig {
|
|
|
418
553
|
|
|
419
554
|
// UserRole.ts
|
|
420
555
|
export type UserRole = "admin" | "editor" | "viewer";
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
556
|
+
"""
|
|
557
|
+
] ;
|
|
558
|
+
cs:do [
|
|
559
|
+
cs:description "Name files after the single class they export" ;
|
|
560
|
+
cs:language "typescript" ;
|
|
561
|
+
cs:code """
|
|
425
562
|
// EventEmitter.ts
|
|
426
563
|
export class EventEmitter {
|
|
427
564
|
// ...
|
|
428
565
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
566
|
+
"""
|
|
567
|
+
] ;
|
|
568
|
+
cs:do [
|
|
569
|
+
cs:description "Name files after the single constant they export" ;
|
|
570
|
+
cs:language "typescript" ;
|
|
571
|
+
cs:code """
|
|
433
572
|
// DEFAULT_TIMEOUT.ts
|
|
434
573
|
export const DEFAULT_TIMEOUT = 5000;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
cs:
|
|
438
|
-
|
|
439
|
-
|
|
574
|
+
"""
|
|
575
|
+
] ;
|
|
576
|
+
cs:dont [
|
|
577
|
+
cs:description "Use generic names like `helpers.ts`, `utils.ts`, or `types.ts` for files with a single export" ;
|
|
578
|
+
cs:language "typescript" ;
|
|
579
|
+
cs:code """
|
|
440
580
|
// helpers.ts — wrong: should be isAccepted.ts
|
|
441
581
|
export const isAccepted = (status: string): boolean => {
|
|
442
582
|
return status === "accepted";
|
|
@@ -446,10 +586,12 @@ export const isAccepted = (status: string): boolean => {
|
|
|
446
586
|
export default function formatCurrency(value: number): string {
|
|
447
587
|
return `$${value.toFixed(2)}`;
|
|
448
588
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
589
|
+
"""
|
|
590
|
+
] ;
|
|
591
|
+
cs:dont [
|
|
592
|
+
cs:description "Use names that describe the domain instead of the export" ;
|
|
593
|
+
cs:language "typescript" ;
|
|
594
|
+
cs:code """
|
|
453
595
|
// validation.ts — wrong: should be isAccepted.ts
|
|
454
596
|
export const isAccepted = (status: string): boolean => {
|
|
455
597
|
return status === "accepted";
|
|
@@ -460,5 +602,121 @@ export interface ConnectionConfig {
|
|
|
460
602
|
host: string;
|
|
461
603
|
port: number;
|
|
462
604
|
}
|
|
463
|
-
|
|
464
|
-
|
|
605
|
+
"""
|
|
606
|
+
] .
|
|
607
|
+
|
|
608
|
+
# Intra-Package Cross-Domain Imports
|
|
609
|
+
cs:IntraPackageCrossDomainImports a cs:CodeStandard ;
|
|
610
|
+
cs:name "packaging/import/cross-domain" ;
|
|
611
|
+
cs:hasCategory cs:PackagingCategory ;
|
|
612
|
+
cs:description """Intra-package cross-domain imports must use Node.js subpath imports (the `imports` field in `package.json`) with the `#` prefix convention. Each `#` alias points directly to the domain's barrel file — no wildcard splats, no path suffixes, no extension mapping. With `moduleResolution: "nodenext"` (or `"node16"`), bare specifiers like `error/index.js` are treated as package names, not path-relative imports. The `paths` compiler option is a manual alias table — brittle and verbose. Subpath imports are the official Node.js mechanism, work natively with Node, Bun, vitest, and tsc, and require no plugins. TypeScript resolves them when `resolvePackageJsonImports` is enabled (on by default with `moduleResolution` set to `node16`, `nodenext`, or `bundler`).""" ;
|
|
613
|
+
cs:do [
|
|
614
|
+
cs:description "Map each `#` alias directly to the domain barrel in `package.json` — no wildcards, no path suffixes" ;
|
|
615
|
+
cs:language "json" ;
|
|
616
|
+
cs:code """
|
|
617
|
+
{
|
|
618
|
+
"imports": {
|
|
619
|
+
"#config": "./src/config/index.ts",
|
|
620
|
+
"#error": "./src/error/index.ts",
|
|
621
|
+
"#pipeline": "./src/pipeline/index.ts",
|
|
622
|
+
"#package-manager": "./src/package-manager/index.ts",
|
|
623
|
+
"#constants": "./src/constants.ts"
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
"""
|
|
627
|
+
] ;
|
|
628
|
+
cs:do [
|
|
629
|
+
cs:description "Import cross-domain modules using the `#` alias — clean, no path suffixes" ;
|
|
630
|
+
cs:language "typescript" ;
|
|
631
|
+
cs:code """
|
|
632
|
+
// src/pipeline/runPipeline.ts
|
|
633
|
+
import { PragmaError } from "#error";
|
|
634
|
+
import { resolveConfig } from "#config";
|
|
635
|
+
"""
|
|
636
|
+
] ;
|
|
637
|
+
cs:do [
|
|
638
|
+
cs:description """Use `moduleResolution: "nodenext"` (or `"node16"` / `"bundler"`) in `tsconfig.json` so TypeScript resolves subpath imports via `resolvePackageJsonImports` (enabled by default with these settings)""" ;
|
|
639
|
+
cs:language "json" ;
|
|
640
|
+
cs:code """
|
|
641
|
+
{
|
|
642
|
+
"compilerOptions": {
|
|
643
|
+
"moduleResolution": "nodenext"
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
"""
|
|
647
|
+
] ;
|
|
648
|
+
cs:do [
|
|
649
|
+
cs:description "Use conditional subpath imports when you need different resolutions for different environments" ;
|
|
650
|
+
cs:language "json" ;
|
|
651
|
+
cs:code """
|
|
652
|
+
{
|
|
653
|
+
"imports": {
|
|
654
|
+
"#db": {
|
|
655
|
+
"development": "./src/db/mock.ts",
|
|
656
|
+
"default": "./src/db/real.ts"
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
"""
|
|
661
|
+
] ;
|
|
662
|
+
cs:dont [
|
|
663
|
+
cs:description "Use wildcard splats or path suffixes in `#` aliases — point directly to the barrel instead" ;
|
|
664
|
+
cs:language "json" ;
|
|
665
|
+
cs:code """
|
|
666
|
+
// Bad: wildcard mapping forces consumers to know internal file structure
|
|
667
|
+
{
|
|
668
|
+
"imports": {
|
|
669
|
+
"#config/*": "./src/config/*",
|
|
670
|
+
"#error/*": "./src/error/*"
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Bad: path suffix leaks barrel structure into every import site
|
|
675
|
+
import { PragmaError } from "#error/index.js";
|
|
676
|
+
"""
|
|
677
|
+
] ;
|
|
678
|
+
cs:dont [
|
|
679
|
+
cs:description "Use bare specifiers without the `#` prefix for intra-package imports — they resolve as package names under `nodenext`" ;
|
|
680
|
+
cs:language "typescript" ;
|
|
681
|
+
cs:code """
|
|
682
|
+
// Bad: "error/index.js" resolves as the npm package "error", not ./src/error/
|
|
683
|
+
import { PragmaError } from "error/index.js";
|
|
684
|
+
"""
|
|
685
|
+
] ;
|
|
686
|
+
cs:dont [
|
|
687
|
+
cs:description "Use `paths` in `tsconfig.json` as a substitute for subpath imports — it's a manual alias table that doesn't participate in Node.js resolution" ;
|
|
688
|
+
cs:language "json" ;
|
|
689
|
+
cs:code """
|
|
690
|
+
// Bad: brittle, verbose, requires a bundler or ts-patch to work at runtime
|
|
691
|
+
{
|
|
692
|
+
"compilerOptions": {
|
|
693
|
+
"baseUrl": ".",
|
|
694
|
+
"paths": {
|
|
695
|
+
"@error/*": ["src/error/*"],
|
|
696
|
+
"@config/*": ["src/config/*"],
|
|
697
|
+
"@pipeline/*": ["src/pipeline/*"]
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
"""
|
|
702
|
+
] ;
|
|
703
|
+
cs:dont [
|
|
704
|
+
cs:description "Use deep relative paths to reach across domains — they break when folders move and obscure the dependency graph" ;
|
|
705
|
+
cs:language "typescript" ;
|
|
706
|
+
cs:code """
|
|
707
|
+
// Bad: fragile, hard to refactor
|
|
708
|
+
import { PragmaError } from "../../error/PragmaError.js";
|
|
709
|
+
import { resolveConfig } from "../../../config/resolveConfig.js";
|
|
710
|
+
"""
|
|
711
|
+
] ;
|
|
712
|
+
cs:dont [
|
|
713
|
+
cs:description "Import specific files through the `#` alias — go through the barrel instead" ;
|
|
714
|
+
cs:language "typescript" ;
|
|
715
|
+
cs:code """
|
|
716
|
+
// Bad: bypasses the barrel, couples to internal file structure
|
|
717
|
+
import { readConfig } from "#config/readConfig.js";
|
|
718
|
+
|
|
719
|
+
// Good: import through the barrel
|
|
720
|
+
import { readConfig } from "#config";
|
|
721
|
+
"""
|
|
722
|
+
] .
|