@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
|
@@ -89,36 +89,51 @@ cs:YourStandardName a cs:CodeStandard ;
|
|
|
89
89
|
cs:name "category/domain/topic" ;
|
|
90
90
|
cs:hasCategory cs:CategoryName ;
|
|
91
91
|
cs:description "Clear, concise description of what this standard covers and why it matters." ;
|
|
92
|
-
cs:
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
cs:do [
|
|
93
|
+
cs:description "First recommended practice." ;
|
|
94
|
+
cs:language "typescript" ;
|
|
95
|
+
cs:code """
|
|
95
96
|
// Example showing the correct approach
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
"""
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
const good = true;
|
|
98
|
+
"""
|
|
99
|
+
] ;
|
|
100
|
+
cs:do [
|
|
101
|
+
cs:description "Second recommended practice." ;
|
|
102
|
+
cs:language "typescript" ;
|
|
103
|
+
cs:code """
|
|
104
|
+
// Another correct example
|
|
105
|
+
const alsoGood = true;
|
|
106
|
+
"""
|
|
107
|
+
] ;
|
|
108
|
+
cs:dont [
|
|
109
|
+
cs:description "First anti-pattern to avoid." ;
|
|
110
|
+
cs:language "typescript" ;
|
|
111
|
+
cs:code """
|
|
106
112
|
// Example showing what NOT to do
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
const bad = true;
|
|
114
|
+
"""
|
|
115
|
+
] ;
|
|
116
|
+
cs:dont [
|
|
117
|
+
cs:description "Second anti-pattern." ;
|
|
118
|
+
cs:language "typescript" ;
|
|
119
|
+
cs:code """
|
|
111
120
|
// Another bad example
|
|
121
|
+
const alsoBad = true;
|
|
122
|
+
"""
|
|
123
|
+
] .
|
|
112
124
|
```
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
|
|
126
|
+
Each `cs:do` and `cs:dont` is a blank node (`cs:Example`) with structured fields:
|
|
127
|
+
- `cs:description` — What the example demonstrates (required)
|
|
128
|
+
- `cs:language` — Language of the code block, e.g. `"typescript"`, `"rust"`, `"css"`, `"bash"`, `"svg"` (optional, omit when no code)
|
|
129
|
+
- `cs:code` — The code content (optional, omit for description-only examples)
|
|
115
130
|
|
|
116
131
|
**Required Properties:**
|
|
117
132
|
- `cs:name` - Hierarchical identifier (must match NamePattern)
|
|
118
133
|
- `cs:hasCategory` - Reference to a Category instance
|
|
119
134
|
- `cs:description` - What and why (plain text or markdown)
|
|
120
|
-
- `cs:
|
|
121
|
-
- `cs:
|
|
135
|
+
- `cs:do` - One or more positive examples (blank nodes)
|
|
136
|
+
- `cs:dont` - One or more negative examples (blank nodes)
|
|
122
137
|
|
|
123
138
|
**Optional Properties:**
|
|
124
139
|
- `cs:extends` - Reference to a parent standard this builds upon
|
|
@@ -144,7 +159,20 @@ cs:SpecificStandard a cs:CodeStandard ;
|
|
|
144
159
|
cs:extends cs:ComponentProps ; # Reference to parent
|
|
145
160
|
cs:hasCategory cs:ReactCategory ;
|
|
146
161
|
cs:description "Specific guidance that builds on the general props standard." ;
|
|
147
|
-
|
|
162
|
+
cs:do [
|
|
163
|
+
cs:description "Example of the specific pattern." ;
|
|
164
|
+
cs:language "typescript" ;
|
|
165
|
+
cs:code """
|
|
166
|
+
// ...
|
|
167
|
+
"""
|
|
168
|
+
] ;
|
|
169
|
+
cs:dont [
|
|
170
|
+
cs:description "Anti-pattern to avoid." ;
|
|
171
|
+
cs:language "typescript" ;
|
|
172
|
+
cs:code """
|
|
173
|
+
// ...
|
|
174
|
+
"""
|
|
175
|
+
] .
|
|
148
176
|
```
|
|
149
177
|
|
|
150
178
|
Query to find potential parent standards:
|
|
@@ -201,17 +229,19 @@ Add new standards to the file matching their category.
|
|
|
201
229
|
- Keep it concise but complete
|
|
202
230
|
- Use markdown for complex descriptions
|
|
203
231
|
|
|
204
|
-
### Do
|
|
205
|
-
-
|
|
232
|
+
### Do Examples Guidelines
|
|
233
|
+
- Each `cs:do` blank node is one discrete positive example
|
|
234
|
+
- `cs:description` explains what the example demonstrates
|
|
235
|
+
- `cs:language` must match the code block language (e.g. `"typescript"`, `"css"`, `"rust"`)
|
|
236
|
+
- `cs:code` contains the actual code — no markdown fences needed
|
|
206
237
|
- Provide concrete, runnable examples
|
|
207
238
|
- Show the COMPLETE correct pattern
|
|
208
|
-
- Explain the reasoning when not obvious
|
|
209
239
|
|
|
210
|
-
### Don'
|
|
211
|
-
-
|
|
240
|
+
### Don't Examples Guidelines
|
|
241
|
+
- Each `cs:dont` blank node is one discrete negative example
|
|
212
242
|
- Show realistic mistakes developers make
|
|
213
|
-
-
|
|
214
|
-
- Mirror the structure of the do
|
|
243
|
+
- `cs:description` should explain WHY it's problematic
|
|
244
|
+
- Mirror the structure of the do examples where possible
|
|
215
245
|
|
|
216
246
|
### Example Quality Checklist
|
|
217
247
|
- [ ] Examples are syntactically correct
|
|
@@ -231,9 +261,10 @@ cs:ErrorBoundaryUsage a cs:CodeStandard ;
|
|
|
231
261
|
cs:name "react/component/error-boundaries" ;
|
|
232
262
|
cs:hasCategory cs:ReactCategory ;
|
|
233
263
|
cs:description "Error boundaries must be used to catch JavaScript errors in component trees and display fallback UI. They should be placed strategically to isolate failures without breaking the entire application." ;
|
|
234
|
-
cs:
|
|
235
|
-
|
|
236
|
-
|
|
264
|
+
cs:do [
|
|
265
|
+
cs:description "Wrap feature sections with error boundaries to isolate failures." ;
|
|
266
|
+
cs:language "tsx" ;
|
|
267
|
+
cs:code """
|
|
237
268
|
const Dashboard = () => (
|
|
238
269
|
<div className="ds dashboard">
|
|
239
270
|
<ErrorBoundary fallback={<WidgetError />}>
|
|
@@ -244,10 +275,12 @@ const Dashboard = () => (
|
|
|
244
275
|
</ErrorBoundary>
|
|
245
276
|
</div>
|
|
246
277
|
);
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
278
|
+
"""
|
|
279
|
+
] ;
|
|
280
|
+
cs:do [
|
|
281
|
+
cs:description "Provide meaningful fallback UI that helps users understand and recover." ;
|
|
282
|
+
cs:language "tsx" ;
|
|
283
|
+
cs:code """
|
|
251
284
|
const WidgetError = () => (
|
|
252
285
|
<div className="ds widget-error">
|
|
253
286
|
<p>This section couldn't load.</p>
|
|
@@ -256,11 +289,12 @@ const WidgetError = () => (
|
|
|
256
289
|
</button>
|
|
257
290
|
</div>
|
|
258
291
|
);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
cs:
|
|
262
|
-
|
|
263
|
-
|
|
292
|
+
"""
|
|
293
|
+
] ;
|
|
294
|
+
cs:dont [
|
|
295
|
+
cs:description "Wrap the entire application in a single error boundary." ;
|
|
296
|
+
cs:language "tsx" ;
|
|
297
|
+
cs:code """
|
|
264
298
|
// Bad: One error anywhere crashes everything
|
|
265
299
|
const App = () => (
|
|
266
300
|
<ErrorBoundary>
|
|
@@ -269,14 +303,16 @@ const App = () => (
|
|
|
269
303
|
<Footer />
|
|
270
304
|
</ErrorBoundary>
|
|
271
305
|
);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
306
|
+
"""
|
|
307
|
+
] ;
|
|
308
|
+
cs:dont [
|
|
309
|
+
cs:description "Use generic or unhelpful fallback messages." ;
|
|
310
|
+
cs:language "tsx" ;
|
|
311
|
+
cs:code """
|
|
276
312
|
// Bad: Doesn't help the user
|
|
277
313
|
const fallback = <div>Something went wrong</div>;
|
|
278
|
-
|
|
279
|
-
|
|
314
|
+
"""
|
|
315
|
+
] .
|
|
280
316
|
```
|
|
281
317
|
|
|
282
318
|
## Tips
|
|
@@ -4,17 +4,23 @@
|
|
|
4
4
|
* Usage: bun src/scripts/generate-docs.ts [output-dir]
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { basename, join } from "node:path";
|
|
9
9
|
|
|
10
10
|
const DATA_DIR = join(import.meta.dir, "../../data");
|
|
11
11
|
const DEFAULT_OUTPUT = "./docs";
|
|
12
12
|
|
|
13
|
+
interface Example {
|
|
14
|
+
description: string;
|
|
15
|
+
language: string;
|
|
16
|
+
code: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
interface Standard {
|
|
14
20
|
name: string;
|
|
15
21
|
description: string;
|
|
16
|
-
dos:
|
|
17
|
-
donts:
|
|
22
|
+
dos: Example[];
|
|
23
|
+
donts: Example[];
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
interface Category {
|
|
@@ -23,15 +29,15 @@ interface Category {
|
|
|
23
29
|
standards: Standard[];
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
//
|
|
32
|
+
// Extract a single string literal (single-line or triple-quoted)
|
|
27
33
|
function extractLiteral(content: string, predicate: string): string {
|
|
28
34
|
const regex = new RegExp(`${predicate}\\s+"""([\\s\\S]*?)"""`, "g");
|
|
29
35
|
const match = regex.exec(content);
|
|
30
|
-
if (match) return match[1].trim();
|
|
36
|
+
if (match?.[1]) return match[1].trim();
|
|
31
37
|
|
|
32
38
|
const singleLine = new RegExp(`${predicate}\\s+"([^"]*)"`, "g");
|
|
33
39
|
const singleMatch = singleLine.exec(content);
|
|
34
|
-
return singleMatch
|
|
40
|
+
return singleMatch?.[1]?.trim() ?? "";
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
function parseCategory(content: string): { label: string; slug: string } {
|
|
@@ -43,6 +49,61 @@ function parseCategory(content: string): { label: string; slug: string } {
|
|
|
43
49
|
return { label, slug };
|
|
44
50
|
}
|
|
45
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Parse blank node examples from a standard block for a given predicate (cs:do or cs:dont).
|
|
54
|
+
*/
|
|
55
|
+
function parseExamples(
|
|
56
|
+
block: string,
|
|
57
|
+
predicate: "cs:do" | "cs:dont",
|
|
58
|
+
): Example[] {
|
|
59
|
+
const examples: Example[] = [];
|
|
60
|
+
|
|
61
|
+
// Match blank node patterns: predicate [ ... ]
|
|
62
|
+
// We need to handle nested triple-quoted strings inside the blank node
|
|
63
|
+
const escapedPred = predicate.replace(".", "\\.");
|
|
64
|
+
const regex = new RegExp(`${escapedPred}\\s*\\[`, "g");
|
|
65
|
+
let match = regex.exec(block);
|
|
66
|
+
|
|
67
|
+
while (match !== null) {
|
|
68
|
+
const startIdx = match.index + match[0].length;
|
|
69
|
+
|
|
70
|
+
// Find the matching closing bracket, accounting for nested triple-quoted strings
|
|
71
|
+
let depth = 1;
|
|
72
|
+
let i = startIdx;
|
|
73
|
+
let inTripleQuote = false;
|
|
74
|
+
|
|
75
|
+
while (i < block.length && depth > 0) {
|
|
76
|
+
if (!inTripleQuote) {
|
|
77
|
+
if (block.slice(i, i + 3) === '"""') {
|
|
78
|
+
inTripleQuote = true;
|
|
79
|
+
i += 3;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (block[i] === "[") depth++;
|
|
83
|
+
if (block[i] === "]") depth--;
|
|
84
|
+
} else {
|
|
85
|
+
if (block.slice(i, i + 3) === '"""') {
|
|
86
|
+
inTripleQuote = false;
|
|
87
|
+
i += 3;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const blankNodeContent = block.slice(startIdx, i - 1);
|
|
95
|
+
|
|
96
|
+
const description = extractLiteral(blankNodeContent, "cs:description");
|
|
97
|
+
const language = extractLiteral(blankNodeContent, "cs:language");
|
|
98
|
+
const code = extractLiteral(blankNodeContent, "cs:code");
|
|
99
|
+
|
|
100
|
+
examples.push({ description, language, code });
|
|
101
|
+
match = regex.exec(block);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return examples;
|
|
105
|
+
}
|
|
106
|
+
|
|
46
107
|
function parseStandards(content: string): Standard[] {
|
|
47
108
|
// Split by standard definitions
|
|
48
109
|
const blocks = content.split(/\n(?=\w+:\w+\s+a\s+cs:CodeStandard)/);
|
|
@@ -53,8 +114,8 @@ function parseStandards(content: string): Standard[] {
|
|
|
53
114
|
|
|
54
115
|
const name = extractLiteral(block, "cs:name");
|
|
55
116
|
const description = extractLiteral(block, "cs:description");
|
|
56
|
-
const dos =
|
|
57
|
-
const donts =
|
|
117
|
+
const dos = parseExamples(block, "cs:do");
|
|
118
|
+
const donts = parseExamples(block, "cs:dont");
|
|
58
119
|
|
|
59
120
|
if (name) {
|
|
60
121
|
standards.push({ name, description, dos, donts });
|
|
@@ -64,6 +125,21 @@ function parseStandards(content: string): Standard[] {
|
|
|
64
125
|
return standards.sort((a, b) => a.name.localeCompare(b.name));
|
|
65
126
|
}
|
|
66
127
|
|
|
128
|
+
function renderExample(example: Example): string {
|
|
129
|
+
const parts: string[] = [];
|
|
130
|
+
|
|
131
|
+
if (example.description) {
|
|
132
|
+
parts.push(example.description);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (example.code) {
|
|
136
|
+
const lang = example.language || "";
|
|
137
|
+
parts.push(`\`\`\`${lang}\n${example.code}\n\`\`\``);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return parts.join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
67
143
|
function generateMarkdown(category: Category): string {
|
|
68
144
|
const lines: string[] = [
|
|
69
145
|
`# ${category.label} Standards`,
|
|
@@ -75,11 +151,17 @@ function generateMarkdown(category: Category): string {
|
|
|
75
151
|
for (const std of category.standards) {
|
|
76
152
|
lines.push(`## ${std.name}`, "", std.description, "");
|
|
77
153
|
|
|
78
|
-
if (std.dos) {
|
|
79
|
-
lines.push("### Do", ""
|
|
154
|
+
if (std.dos.length > 0) {
|
|
155
|
+
lines.push("### Do", "");
|
|
156
|
+
for (const ex of std.dos) {
|
|
157
|
+
lines.push(renderExample(ex), "");
|
|
158
|
+
}
|
|
80
159
|
}
|
|
81
|
-
if (std.donts) {
|
|
82
|
-
lines.push("### Don't", ""
|
|
160
|
+
if (std.donts.length > 0) {
|
|
161
|
+
lines.push("### Don't", "");
|
|
162
|
+
for (const ex of std.donts) {
|
|
163
|
+
lines.push(renderExample(ex), "");
|
|
164
|
+
}
|
|
83
165
|
}
|
|
84
166
|
lines.push("---", "");
|
|
85
167
|
}
|
package/src/scripts/index.ts
CHANGED
|
@@ -9,11 +9,13 @@ const command = process.argv[2];
|
|
|
9
9
|
switch (command) {
|
|
10
10
|
case "docs":
|
|
11
11
|
case "generate-docs":
|
|
12
|
-
await import("./generate-docs.
|
|
12
|
+
await import("./generate-docs.js");
|
|
13
13
|
break;
|
|
14
14
|
default:
|
|
15
15
|
console.log("Usage: bun src/scripts/index.ts <command>");
|
|
16
16
|
console.log("");
|
|
17
17
|
console.log("Commands:");
|
|
18
|
-
console.log(
|
|
18
|
+
console.log(
|
|
19
|
+
" docs [output-dir] Generate markdown docs (default: ./docs)",
|
|
20
|
+
);
|
|
19
21
|
}
|