@canonical/code-standards 0.1.0
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/.mcp.json +8 -0
- package/README.md +297 -0
- package/data/code.ttl +437 -0
- package/data/css.ttl +265 -0
- package/data/icons.ttl +359 -0
- package/data/packaging.ttl +464 -0
- package/data/react.ttl +752 -0
- package/data/rust.ttl +1806 -0
- package/data/storybook.ttl +403 -0
- package/data/styling.ttl +165 -0
- package/data/tsdoc.ttl +216 -0
- package/data/turtle.ttl +179 -0
- package/definitions/CodeStandard.ttl +80 -0
- package/docs/code.md +720 -0
- package/docs/css.md +275 -0
- package/docs/icons.md +367 -0
- package/docs/index.md +15 -0
- package/docs/react.md +766 -0
- package/docs/rust.md +1784 -0
- package/docs/storybook.md +413 -0
- package/docs/styling.md +163 -0
- package/docs/tsdoc.md +213 -0
- package/docs/turtle.md +179 -0
- package/package.json +9 -0
- package/skills/add-standard/SKILL.md +288 -0
- package/src/scripts/generate-docs.ts +131 -0
- package/src/scripts/index.ts +19 -0
package/data/code.ttl
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
@prefix cs: <http://pragma.canonical.com/codestandards#> .
|
|
2
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
3
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
4
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
5
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
6
|
+
|
|
7
|
+
# General Code Category
|
|
8
|
+
cs:CodeCategory a cs:Category ;
|
|
9
|
+
rdfs:label "Code"@en ;
|
|
10
|
+
rdfs:comment "General typescript coding standards and best practices"@en ;
|
|
11
|
+
cs:slug "code" .
|
|
12
|
+
|
|
13
|
+
# Function Purity Standard
|
|
14
|
+
cs:FunctionPurity a cs:CodeStandard ;
|
|
15
|
+
cs:name "code/function/purity" ;
|
|
16
|
+
cs:hasCategory cs:CodeCategory ;
|
|
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
|
|
21
|
+
const calculatePrice = (basePrice: number, taxRate: number): number => {
|
|
22
|
+
return basePrice * (1 + taxRate);
|
|
23
|
+
};
|
|
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
|
|
28
|
+
/**
|
|
29
|
+
* Writes data to the file system.
|
|
30
|
+
* @note - This function is impure - it modifies the file system.
|
|
31
|
+
*/
|
|
32
|
+
function writeOutputToFile(data: string, path: string) {
|
|
33
|
+
fs.writeFileSync(path, data);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
""" ;
|
|
37
|
+
cs:donts """
|
|
38
|
+
(Don't) Create impure functions without annotation or documentation.
|
|
39
|
+
```typescript
|
|
40
|
+
let total = 0;
|
|
41
|
+
const addToTotal = (value: number) => {
|
|
42
|
+
total += value; // Modifies external state, not documented
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
(Don't) Hide side effects in functions that appear pure.
|
|
47
|
+
```typescript
|
|
48
|
+
function getConfig() {
|
|
49
|
+
// Reads from disk without annotation
|
|
50
|
+
return fs.readFileSync('config.json');
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
""" .
|
|
54
|
+
|
|
55
|
+
# Function Composition Standard
|
|
56
|
+
cs:FunctionComposition a cs:CodeStandard ;
|
|
57
|
+
cs:name "code/function/composition" ;
|
|
58
|
+
cs:hasCategory cs:CodeCategory ;
|
|
59
|
+
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
|
|
63
|
+
const validateEmail = (email: string): boolean => {
|
|
64
|
+
return /^[^@]+@[^@]+\\.[^@]+$/.test(email);
|
|
65
|
+
};
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
(Do) Compose single-purpose functions to build complex logic.
|
|
69
|
+
```typescript
|
|
70
|
+
const validatePassword = (password: string): boolean => {
|
|
71
|
+
return password.length >= 8;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const validateForm = (email: string, password:string): boolean => {
|
|
75
|
+
return validateEmail(email) && validatePassword(password);
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
""" ;
|
|
79
|
+
cs:donts """
|
|
80
|
+
(Don't) Mix multiple responsibilities in a single function.
|
|
81
|
+
```typescript
|
|
82
|
+
const processUserData = (user: any) => {
|
|
83
|
+
// Validates user data
|
|
84
|
+
// Transforms the data
|
|
85
|
+
// Saves to the database
|
|
86
|
+
// Sends a notification
|
|
87
|
+
};
|
|
88
|
+
```
|
|
89
|
+
""" .
|
|
90
|
+
|
|
91
|
+
# Function Location Standard
|
|
92
|
+
cs:FunctionLocation a cs:CodeStandard ;
|
|
93
|
+
cs:name "code/function/location" ;
|
|
94
|
+
cs:hasCategory cs:CodeCategory ;
|
|
95
|
+
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
|
|
99
|
+
// utils/math.ts
|
|
100
|
+
export function calculateSum(a: number, b: number): number {
|
|
101
|
+
return a + b;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Used in multiple places
|
|
105
|
+
import { calculateSum } from './utils/math.js';
|
|
106
|
+
|
|
107
|
+
// 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
|
|
124
|
+
// utils/validators.ts
|
|
125
|
+
export const validateUser = (user: User) => { ... }; // Only used in one component
|
|
126
|
+
|
|
127
|
+
// services/UserService.ts
|
|
128
|
+
import { validateUser } from '../utils/validators.js'; // Less ideal if only used here
|
|
129
|
+
|
|
130
|
+
// Don't mix unrelated functions in a single file:
|
|
131
|
+
```typescript
|
|
132
|
+
export const mathFunc = () => {};
|
|
133
|
+
export const stringFunc = () => {};
|
|
134
|
+
""" .
|
|
135
|
+
|
|
136
|
+
# API Stability Standard
|
|
137
|
+
cs:APIStability a cs:CodeStandard ;
|
|
138
|
+
cs:name "code/api/stability" ;
|
|
139
|
+
cs:hasCategory cs:CodeCategory ;
|
|
140
|
+
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
|
|
144
|
+
/**
|
|
145
|
+
* Configuration for the data processing pipeline.
|
|
146
|
+
* @experimental The streaming API is experimental and may change
|
|
147
|
+
* in future releases. Currently, it only supports JSON data.
|
|
148
|
+
*/
|
|
149
|
+
interface PipelineConfig {
|
|
150
|
+
// ...
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
(Do) Add an @experimental tag to a specific property with context about its experimental status.
|
|
155
|
+
```typescript
|
|
156
|
+
interface PipelineConfig {
|
|
157
|
+
/**
|
|
158
|
+
* @experimental The custom transformers API is in beta, and the interface
|
|
159
|
+
* may change to support stronger type validation.
|
|
160
|
+
*/
|
|
161
|
+
transformers?: DataTransformer[];
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
(Do) Describe the experimental status and any future plans for the API.
|
|
166
|
+
```typescript
|
|
167
|
+
interface CacheConfig {
|
|
168
|
+
/**
|
|
169
|
+
* @experimental The distributed cache API is experimental and will be
|
|
170
|
+
* replaced with a new consensus-based implementation in v2.1.
|
|
171
|
+
*/
|
|
172
|
+
distributed?: boolean;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
""" ;
|
|
176
|
+
cs:donts """
|
|
177
|
+
(Don't) Use the @experimental tag without an explanation.
|
|
178
|
+
```typescript
|
|
179
|
+
interface ProcessorConfig {
|
|
180
|
+
/** @experimental */ // Bad: No context provided.
|
|
181
|
+
streaming?: boolean;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
(Don't) Use the @experimental tag without describing what is experimental.
|
|
186
|
+
```typescript
|
|
187
|
+
/**
|
|
188
|
+
* @experimental // Bad: No description of what is experimental.
|
|
189
|
+
*/
|
|
190
|
+
interface QueueConfig {
|
|
191
|
+
processor: (item: any) => Promise<void>;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
""" .
|
|
195
|
+
|
|
196
|
+
# Function Verb Naming Standard
|
|
197
|
+
cs:FunctionVerbNaming a cs:CodeStandard ;
|
|
198
|
+
cs:name "code/naming/function-verb" ;
|
|
199
|
+
cs:hasCategory cs:CodeCategory ;
|
|
200
|
+
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
|
|
204
|
+
const findPathToModule = (name: string): string => { /* ... */ };
|
|
205
|
+
const fetchDataForUser = (userId: string): Promise<User> => { /* ... */ };
|
|
206
|
+
const parseConfigFile = (path: string): Config => { /* ... */ };
|
|
207
|
+
const calculateTotalPrice = (items: Item[]): number => { /* ... */ };
|
|
208
|
+
const isValidEmail = (email: string): boolean => { /* ... */ };
|
|
209
|
+
const hasPermission = (user: User, action: string): boolean => { /* ... */ };
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
(Do) Use descriptive verb prefixes that convey the operation:
|
|
213
|
+
```typescript
|
|
214
|
+
// Retrieval: get, fetch, find, resolve, load, read
|
|
215
|
+
const getUser = (id: string): User => { /* ... */ };
|
|
216
|
+
const fetchOrders = (): Promise<Order[]> => { /* ... */ };
|
|
217
|
+
const findMatchingRule = (rules: Rule[], input: string): Rule | undefined => { /* ... */ };
|
|
218
|
+
|
|
219
|
+
// Transformation: format, parse, convert, transform, map, normalize
|
|
220
|
+
const formatDate = (date: Date): string => { /* ... */ };
|
|
221
|
+
const parseResponse = (raw: string): Response => { /* ... */ };
|
|
222
|
+
|
|
223
|
+
// Validation: is, has, can, should, validate, check
|
|
224
|
+
const isActive = (user: User): boolean => { /* ... */ };
|
|
225
|
+
const hasExpired = (token: Token): boolean => { /* ... */ };
|
|
226
|
+
const validateInput = (data: unknown): data is FormData => { /* ... */ };
|
|
227
|
+
|
|
228
|
+
// Mutation: set, update, add, remove, delete, reset, clear
|
|
229
|
+
const setTheme = (theme: Theme): void => { /* ... */ };
|
|
230
|
+
const removeItem = (id: string): void => { /* ... */ };
|
|
231
|
+
|
|
232
|
+
// Creation: create, build, generate, compose, make
|
|
233
|
+
const createConnection = (config: Config): Connection => { /* ... */ };
|
|
234
|
+
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
|
|
240
|
+
// Bad: reads like a variable, not a function
|
|
241
|
+
const pathToModule = (name: string): string => { /* ... */ };
|
|
242
|
+
// Good: findPathToModule
|
|
243
|
+
|
|
244
|
+
const dataForUser = (userId: string): Promise<User> => { /* ... */ };
|
|
245
|
+
// Good: fetchDataForUser
|
|
246
|
+
|
|
247
|
+
const userPermissions = (user: User): Permission[] => { /* ... */ };
|
|
248
|
+
// Good: getPermissions or listPermissions
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
(Don't) Use vague or non-descriptive verbs that don't convey meaning:
|
|
252
|
+
```typescript
|
|
253
|
+
// Bad: "do" and "process" are too vague on their own
|
|
254
|
+
const doEmail = (email: string) => { /* ... */ };
|
|
255
|
+
// Good: sendEmail, validateEmail, formatEmail
|
|
256
|
+
|
|
257
|
+
const processUser = (user: User) => { /* ... */ };
|
|
258
|
+
// Good: activateUser, deactivateUser, updateUser
|
|
259
|
+
```
|
|
260
|
+
""" .
|
|
261
|
+
|
|
262
|
+
# Constants File Standard
|
|
263
|
+
cs:ConstantsFile a cs:CodeStandard ;
|
|
264
|
+
cs:name "code/constants/file" ;
|
|
265
|
+
cs:hasCategory cs:CodeCategory ;
|
|
266
|
+
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
|
|
270
|
+
// features/pricing/constants.ts
|
|
271
|
+
export const DEFAULT_CURRENCY = "USD";
|
|
272
|
+
export const TAX_RATE = 0.21;
|
|
273
|
+
export const MAX_DISCOUNT_PERCENT = 50;
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
(Do) Colocate `constants.ts` inside the domain folder it belongs to:
|
|
277
|
+
```
|
|
278
|
+
features/
|
|
279
|
+
├── pricing/
|
|
280
|
+
│ ├── constants.ts # Domain constants live here
|
|
281
|
+
│ ├── calculatePrice.ts
|
|
282
|
+
│ └── index.ts
|
|
283
|
+
├── auth/
|
|
284
|
+
│ ├── constants.ts # Auth-specific constants
|
|
285
|
+
│ ├── validateToken.ts
|
|
286
|
+
│ └── index.ts
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
(Do) Import constants directly from the owning domain's `constants.ts`:
|
|
290
|
+
```typescript
|
|
291
|
+
// features/pricing/calculatePrice.ts
|
|
292
|
+
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
|
|
297
|
+
// constants.ts
|
|
298
|
+
export const RECONNECT_INTERVAL_MS = 5000;
|
|
299
|
+
export const MAX_RETRIES = 3;
|
|
300
|
+
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
(Do) Keep single-use constants inline in the file that uses them:
|
|
304
|
+
```typescript
|
|
305
|
+
// features/pricing/applyDiscount.ts
|
|
306
|
+
const MAX_DISCOUNT_PERCENT = 50;
|
|
307
|
+
|
|
308
|
+
export default function applyDiscount(price: number, rate: number): number {
|
|
309
|
+
const capped = Math.min(rate, MAX_DISCOUNT_PERCENT / 100);
|
|
310
|
+
return price * (1 - capped);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
""" ;
|
|
314
|
+
cs:donts """
|
|
315
|
+
(Don't) Use a default export in a constants file:
|
|
316
|
+
```typescript
|
|
317
|
+
// Bad: default export forces consumers to name the import
|
|
318
|
+
export default {
|
|
319
|
+
DEFAULT_CURRENCY: "USD",
|
|
320
|
+
TAX_RATE: 0.21,
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
(Don't) Scatter constants across unrelated files instead of collecting them in `constants.ts`:
|
|
325
|
+
```typescript
|
|
326
|
+
// Bad: constants buried inside implementation files
|
|
327
|
+
// calculatePrice.ts
|
|
328
|
+
export const TAX_RATE = 0.21;
|
|
329
|
+
const calculatePrice = (base: number) => base * (1 + TAX_RATE);
|
|
330
|
+
export default calculatePrice;
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
(Don't) Place constants far from the domain that owns them:
|
|
334
|
+
```typescript
|
|
335
|
+
// Bad: pricing constants in a top-level shared file
|
|
336
|
+
// src/constants.ts
|
|
337
|
+
export const TAX_RATE = 0.21; // Belongs in pricing/
|
|
338
|
+
export const MAX_RETRIES = 3; // Belongs in network/
|
|
339
|
+
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
|
|
344
|
+
// Bad: constants.ts should only contain constant values
|
|
345
|
+
export const MAX_RETRIES = 3;
|
|
346
|
+
export const formatRetryMessage = (n: number) => `Retry ${n} of ${MAX_RETRIES}`;
|
|
347
|
+
export type RetryConfig = { max: number };
|
|
348
|
+
```
|
|
349
|
+
""" .
|
|
350
|
+
|
|
351
|
+
# Types File Standard
|
|
352
|
+
cs:TypesFile a cs:CodeStandard ;
|
|
353
|
+
cs:name "code/types/file" ;
|
|
354
|
+
cs:hasCategory cs:CodeCategory ;
|
|
355
|
+
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
|
|
359
|
+
// features/pricing/types.ts
|
|
360
|
+
export type Currency = "USD" | "EUR" | "GBP";
|
|
361
|
+
export interface PriceBreakdown {
|
|
362
|
+
base: number;
|
|
363
|
+
tax: number;
|
|
364
|
+
total: number;
|
|
365
|
+
}
|
|
366
|
+
export type DiscountStrategy = "percentage" | "fixed";
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
(Do) Colocate `types.ts` inside the domain folder it belongs to:
|
|
370
|
+
```
|
|
371
|
+
features/
|
|
372
|
+
├── pricing/
|
|
373
|
+
│ ├── types.ts # Shared domain types live here
|
|
374
|
+
│ ├── constants.ts
|
|
375
|
+
│ ├── calculatePrice.ts
|
|
376
|
+
│ └── index.ts
|
|
377
|
+
├── auth/
|
|
378
|
+
│ ├── types.ts # Auth-specific types
|
|
379
|
+
│ ├── validateToken.ts
|
|
380
|
+
│ └── index.ts
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
(Do) Keep single-use types inline in the file that uses them:
|
|
384
|
+
```typescript
|
|
385
|
+
// features/pricing/applyDiscount.ts
|
|
386
|
+
type DiscountResult = { discounted: number; savings: number };
|
|
387
|
+
|
|
388
|
+
export default function applyDiscount(price: number, rate: number): DiscountResult {
|
|
389
|
+
const savings = price * rate;
|
|
390
|
+
return { discounted: price - savings, savings };
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
(Do) Import shared types from the owning domain's `types.ts`:
|
|
395
|
+
```typescript
|
|
396
|
+
// features/pricing/calculatePrice.ts
|
|
397
|
+
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
|
|
403
|
+
// Bad: default export for a collection of types
|
|
404
|
+
export default interface PriceBreakdown {
|
|
405
|
+
base: number;
|
|
406
|
+
tax: number;
|
|
407
|
+
total: number;
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
(Don't) Extract single-use types to `types.ts` when they are only used in one file:
|
|
412
|
+
```typescript
|
|
413
|
+
// Bad: types.ts contains a type only used in applyDiscount.ts
|
|
414
|
+
// types.ts
|
|
415
|
+
export type DiscountResult = { discounted: number; savings: number };
|
|
416
|
+
|
|
417
|
+
// applyDiscount.ts
|
|
418
|
+
import type { DiscountResult } from "./types.js"; // Unnecessary indirection
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
(Don't) Place types far from the domain that owns them:
|
|
422
|
+
```typescript
|
|
423
|
+
// Bad: all types dumped in a top-level shared file
|
|
424
|
+
// src/types.ts
|
|
425
|
+
export type Currency = "USD" | "EUR" | "GBP"; // Belongs in pricing/
|
|
426
|
+
export type AuthToken = { jwt: string }; // Belongs in auth/
|
|
427
|
+
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
|
|
432
|
+
// Bad: types.ts should only contain type declarations
|
|
433
|
+
export type Currency = "USD" | "EUR" | "GBP";
|
|
434
|
+
export const DEFAULT_CURRENCY: Currency = "USD";
|
|
435
|
+
export const formatCurrency = (value: number, currency: Currency) => `${currency} ${value}`;
|
|
436
|
+
```
|
|
437
|
+
""" .
|
package/data/css.ttl
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
@prefix cs: <http://pragma.canonical.com/codestandards#> .
|
|
2
|
+
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
3
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
4
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
5
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
6
|
+
|
|
7
|
+
# CSS Category
|
|
8
|
+
cs:CSSCategory a cs:Category ;
|
|
9
|
+
rdfs:label "CSS"@en ;
|
|
10
|
+
rdfs:comment "Standards for CSS technical implementation"@en ;
|
|
11
|
+
cs:slug "css" .
|
|
12
|
+
|
|
13
|
+
# CSS Selector Namespace Standard
|
|
14
|
+
cs:CSSSelectorNamespace a cs:CodeStandard ;
|
|
15
|
+
cs:name "css/selectors/namespace" ;
|
|
16
|
+
cs:hasCategory cs:CSSCategory ;
|
|
17
|
+
cs:description "All component selectors must be prefixed with the `.ds` namespace (e.g., `.ds.button`)." ;
|
|
18
|
+
cs:dos """
|
|
19
|
+
(Do) Prefix all component selectors with `.ds`.
|
|
20
|
+
```css
|
|
21
|
+
/* Component root with namespace */
|
|
22
|
+
.ds.button {
|
|
23
|
+
/* Base styles */
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
""" ;
|
|
27
|
+
cs:donts """
|
|
28
|
+
(Don't) Omit the `.ds` namespace from component selectors.
|
|
29
|
+
```css
|
|
30
|
+
/* Bad: Missing .ds namespace */
|
|
31
|
+
.button {
|
|
32
|
+
/* styles */
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
""" .
|
|
36
|
+
|
|
37
|
+
# CSS Component Encapsulation Standard
|
|
38
|
+
cs:CSSComponentEncapsulation a cs:CodeStandard ;
|
|
39
|
+
cs:name "css/component/encapsulation" ;
|
|
40
|
+
cs:hasCategory cs:CSSCategory ;
|
|
41
|
+
cs:description "Component styles must be encapsulated using the component's root class as a boundary. All internal element styles must be scoped to the component's namespace." ;
|
|
42
|
+
cs:dos """
|
|
43
|
+
(Do) Scope internal element styles using the component's namespace.
|
|
44
|
+
```css
|
|
45
|
+
.ds.button {
|
|
46
|
+
/* Component root styles */
|
|
47
|
+
|
|
48
|
+
& > .icon {
|
|
49
|
+
/* Internal element styles */
|
|
50
|
+
margin-right: var(--button-icon-spacing);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
""" ;
|
|
55
|
+
cs:donts """
|
|
56
|
+
(Don't) Don't style internal elements without the component namespace.
|
|
57
|
+
```css
|
|
58
|
+
/* Bad: Internal element not scoped to component */
|
|
59
|
+
.icon {
|
|
60
|
+
margin-right: var(--button-icon-spacing);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
""" .
|
|
64
|
+
|
|
65
|
+
# CSS State Handling Standard
|
|
66
|
+
cs:CSSStateHandling a cs:CodeStandard ;
|
|
67
|
+
cs:name "css/component/states" ;
|
|
68
|
+
cs:hasCategory cs:CSSCategory ;
|
|
69
|
+
cs:description "Component states must be handled using attribute selectors for native states and class modifiers for custom states." ;
|
|
70
|
+
cs:dos """
|
|
71
|
+
(Do) Use attribute selectors for native element states.
|
|
72
|
+
```css
|
|
73
|
+
.ds.button {
|
|
74
|
+
&[disabled] {
|
|
75
|
+
opacity: var(--button-disabled-opacity);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
""" ;
|
|
80
|
+
cs:donts """
|
|
81
|
+
(Don't) Use class modifiers for native states.
|
|
82
|
+
```css
|
|
83
|
+
/* Bad: Using class for native state */
|
|
84
|
+
.ds.button.disabled {
|
|
85
|
+
opacity: var(--button-disabled-opacity);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
""" .
|
|
89
|
+
|
|
90
|
+
# CSS Specificity Management Standard
|
|
91
|
+
cs:CSSSpecificityManagement a cs:CodeStandard ;
|
|
92
|
+
cs:name "css/selectors/specificity" ;
|
|
93
|
+
cs:hasCategory cs:CSSCategory ;
|
|
94
|
+
cs:description """CSS selectors must follow a strict specificity pattern:
|
|
95
|
+
- Component root must use namespace + component name (.ds.button)
|
|
96
|
+
- Single modifier class for variants (.ds.button.primary)
|
|
97
|
+
- Single attribute for states (.ds.button[disabled])""" ;
|
|
98
|
+
cs:dos """
|
|
99
|
+
(Do) Use a single modifier class for component variants.
|
|
100
|
+
```css
|
|
101
|
+
.ds.button.primary {
|
|
102
|
+
/* Variant: root + modifier (3 classes) */
|
|
103
|
+
background: var(--button-primary-background);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
""" ;
|
|
107
|
+
cs:donts """
|
|
108
|
+
(Don't) Combine multiple modifiers or mix states with variants.
|
|
109
|
+
```css
|
|
110
|
+
/* Bad: Mixing variant with state */
|
|
111
|
+
.ds.button.primary[disabled].large {
|
|
112
|
+
/* Too specific: root + 2 modifiers + state */
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
""" .
|
|
116
|
+
|
|
117
|
+
# CSS Selector Semantics Standard
|
|
118
|
+
cs:CSSSelectorSemantics a cs:CodeStandard ;
|
|
119
|
+
cs:name "css/selectors/semantics" ;
|
|
120
|
+
cs:hasCategory cs:CSSCategory ;
|
|
121
|
+
cs:description "CSS class names must describe the purpose or state of an element, not its appearance." ;
|
|
122
|
+
cs:dos """
|
|
123
|
+
(Do) Use semantic modifier classes to represent component variations.
|
|
124
|
+
```css
|
|
125
|
+
/* Semantic modifier for a primary button */
|
|
126
|
+
.ds.button.primary {
|
|
127
|
+
--modifier-color: var(--color-primary);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
""" ;
|
|
131
|
+
cs:donts """
|
|
132
|
+
(Don't) Use non-semantic or presentational class names.
|
|
133
|
+
```css
|
|
134
|
+
/* Bad: 'big' describes appearance, not purpose */
|
|
135
|
+
.ds.button.big {
|
|
136
|
+
padding: 1rem;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
""" .
|
|
140
|
+
|
|
141
|
+
# Theme Activation Standard
|
|
142
|
+
cs:ThemeActivation a cs:CodeStandard ;
|
|
143
|
+
cs:name "css/themes/activation" ;
|
|
144
|
+
cs:hasCategory cs:CSSCategory ;
|
|
145
|
+
cs:description """Theme tokens must be activated through CSS classes on container elements. See styling/themes/definition for theme token structure.""" ;
|
|
146
|
+
cs:dos """
|
|
147
|
+
(Do) Define semantic tokens within theme classes.
|
|
148
|
+
```css
|
|
149
|
+
.canonical {
|
|
150
|
+
--spacing-vertical-medium: var(--spacing-unit-2x);
|
|
151
|
+
--color-background: var(--color-neutral-100);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
""" ;
|
|
155
|
+
cs:donts """
|
|
156
|
+
(Don't) Hardcode theme names in component styles.
|
|
157
|
+
```css
|
|
158
|
+
/* Bad: Component locked to specific theme */
|
|
159
|
+
.ds.button {
|
|
160
|
+
padding: var(--canonical-spacing-vertical-medium);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
""" .
|
|
164
|
+
|
|
165
|
+
# CSS Property Values Standard
|
|
166
|
+
cs:CSSPropertyValues a cs:CodeStandard ;
|
|
167
|
+
cs:name "css/properties/values" ;
|
|
168
|
+
cs:hasCategory cs:CSSCategory ;
|
|
169
|
+
cs:description """CSS properties must use design tokens for design decisions (see styling/tokens/creation) and raw values for properties that are independent from design decisions, or "unthemable".""" ;
|
|
170
|
+
cs:dos """
|
|
171
|
+
(Do) Use design tokens for design decisions.
|
|
172
|
+
```css
|
|
173
|
+
.ds.button {
|
|
174
|
+
/* Design decision uses token */
|
|
175
|
+
background: var(--button-background);
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
(Do) Use raw values for unthemable properties (independent of design decisions).
|
|
179
|
+
```css
|
|
180
|
+
.ds.skip-link {
|
|
181
|
+
visibility: hidden;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
""" ;
|
|
185
|
+
cs:donts """
|
|
186
|
+
(Don't) Use raw values for design decisions.
|
|
187
|
+
```css
|
|
188
|
+
.ds.button {
|
|
189
|
+
/* Bad: Design decision using raw value */
|
|
190
|
+
background: #0066CC;
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
""" .
|
|
194
|
+
|
|
195
|
+
# Child Element Naming Standard
|
|
196
|
+
cs:CSSChildElementNaming a cs:CodeStandard ;
|
|
197
|
+
cs:name "css/selectors/child-elements" ;
|
|
198
|
+
cs:hasCategory cs:CSSCategory ;
|
|
199
|
+
cs:description """Child elements within components must use simple, role-based class names (e.g., `.header`, `.content`, `.icon`) scoped by the parent selector, NOT verbose prefixed names that repeat the component name.""" ;
|
|
200
|
+
cs:dos """
|
|
201
|
+
(Do) Use simple role-based class names scoped by the parent.
|
|
202
|
+
```css
|
|
203
|
+
.ds.accordion-item {
|
|
204
|
+
& > .header {
|
|
205
|
+
/* Header styles */
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
& > .header > .chevron {
|
|
209
|
+
/* Chevron indicator */
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
& > .header > .heading {
|
|
213
|
+
/* Heading text */
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
& > .content {
|
|
217
|
+
/* Collapsible content */
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.ds.breadcrumbs-item {
|
|
222
|
+
& > .link {
|
|
223
|
+
/* Link styles */
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
& > .separator {
|
|
227
|
+
/* Separator styles */
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
""" ;
|
|
232
|
+
cs:donts """
|
|
233
|
+
(Don't) Use verbose prefixed class names that repeat the component name.
|
|
234
|
+
```css
|
|
235
|
+
/* Bad: Redundant component prefix in child class names */
|
|
236
|
+
.ds.accordion-item .accordion-item-header {
|
|
237
|
+
/* 'accordion-item-' prefix is redundant */
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.ds.accordion-item .accordion-item-chevron {
|
|
241
|
+
/* Already scoped by parent, no need to repeat */
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.ds.timeline-event .timeline-event-marker {
|
|
245
|
+
/* Verbose and BEM-like */
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
""" .
|
|
249
|
+
|
|
250
|
+
# Component Naming Convention for CSS
|
|
251
|
+
cs:CSSComponentNamingConvention a cs:CodeStandard ;
|
|
252
|
+
cs:name "css/selectors/naming-convention" ;
|
|
253
|
+
cs:hasCategory cs:CSSCategory ;
|
|
254
|
+
cs:description "Component CSS class names must be the kebab-case version of the component's PascalCase name." ;
|
|
255
|
+
cs:dos """
|
|
256
|
+
(Do) Convert PascalCase component names to kebab-case for CSS classes:
|
|
257
|
+
- `MyComponent` -> `.ds.my-component`
|
|
258
|
+
- `UserProfile` -> `.ds.user-profile`
|
|
259
|
+
- `Button` -> `.ds.button`
|
|
260
|
+
""" ;
|
|
261
|
+
cs:donts """
|
|
262
|
+
(Don't) Use PascalCase or other formats in CSS class names:
|
|
263
|
+
- `.ds.MyComponent` (Bad: Not kebab-case)
|
|
264
|
+
- `.ds.user_profile` (Bad: Not kebab-case)
|
|
265
|
+
""" .
|