@ankhorage/paradox 0.1.27 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +53 -61
- package/dist/analyze/analyze.d.ts +2 -2
- package/dist/analyze/analyze.js +51 -35
- package/dist/analyze/badges.d.ts +2 -1
- package/dist/analyze/badges.js +14 -4
- package/dist/analyze/components.js +12 -11
- package/dist/analyze/documentation/collectDocumentationCommentsAsync.d.ts +11 -0
- package/dist/analyze/documentation/collectDocumentationCommentsAsync.js +72 -0
- package/dist/analyze/documentation/findings.d.ts +5 -0
- package/dist/analyze/documentation/findings.js +17 -0
- package/dist/analyze/documentation/validateDocumentationPolicyAsync.d.ts +13 -0
- package/dist/analyze/documentation/validateDocumentationPolicyAsync.js +150 -0
- package/dist/analyze/documentation/validateReferencesAsync.d.ts +11 -0
- package/dist/analyze/documentation/validateReferencesAsync.js +148 -0
- package/dist/analyze/exports.d.ts +4 -0
- package/dist/analyze/exports.js +17 -15
- package/dist/analyze/modules.js +3 -8
- package/dist/analyze/readmeConfig.d.ts +1 -3
- package/dist/analyze/readmeConfig.js +8 -19
- package/dist/analyze/readmeUsage.d.ts +7 -10
- package/dist/analyze/readmeUsage.js +124 -48
- package/dist/analyze/semantic/docBlocks.js +18 -48
- package/dist/analyze/semantic/exports.js +3 -7
- package/dist/analyze/semantic/model.d.ts +0 -2
- package/dist/analyze/semantic/paradoxComment.d.ts +1 -11
- package/dist/analyze/semantic/paradoxComment.js +1 -43
- package/dist/analyze/semantic/tagRegistry.js +2 -1
- package/dist/analyze/sequenceScenarios.js +2 -4
- package/dist/analyze/sourceFunctions.js +4 -4
- package/dist/analyze/types.d.ts +31 -24
- package/dist/analyze/usage.d.ts +2 -2
- package/dist/analyze/usage.js +3 -33
- package/dist/analyze/utils/getExportMetadata.js +11 -40
- package/dist/analyze/utils/parseParadoxComment.d.ts +12 -9
- package/dist/analyze/utils/parseParadoxComment.js +66 -78
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.js +3 -2
- package/dist/cli/standalone.js +11 -0
- package/dist/config/defineParadoxConfig.d.ts +1 -1
- package/dist/doc-tags/registry.d.ts +28 -32
- package/dist/doc-tags/registry.js +35 -39
- package/dist/index.d.ts +1 -1
- package/dist/model/buildModel.d.ts +26 -19
- package/dist/model/buildModel.js +33 -99
- package/dist/model/types.d.ts +27 -20
- package/dist/paths/policy.d.ts +1 -1
- package/dist/render/renderers/diagrams.js +3 -11
- package/dist/render/renderers/html.js +89 -58
- package/dist/render/renderers/markdown.js +139 -86
- package/dist/render/toFileStem.d.ts +2 -0
- package/dist/render/toFileStem.js +8 -0
- package/dist/{config/types.d.ts → types/config.d.ts} +3 -5
- package/dist/write/write.d.ts +1 -1
- package/package.json +2 -1
- package/dist/analyze/readmeCli.d.ts +0 -9
- package/dist/analyze/readmeCli.js +0 -33
- package/dist/analyze/utils/getLeadingParadoxComment.d.ts +0 -10
- package/dist/analyze/utils/getLeadingParadoxComment.js +0 -16
- /package/dist/{config/types.js → types/config.js} +0 -0
package/dist/model/buildModel.js
CHANGED
|
@@ -4,87 +4,45 @@
|
|
|
4
4
|
export function buildModel(analysis) {
|
|
5
5
|
const exportNames = new Set(analysis.exports.map((item) => item.name));
|
|
6
6
|
const exportsByName = new Map(analysis.exports.map((item) => [item.name, mapExport(item, exportNames)]));
|
|
7
|
-
const exports = sortByName([...exportsByName.values()]);
|
|
8
7
|
return {
|
|
9
8
|
packageName: analysis.packageName,
|
|
10
9
|
packageId: analysis.packageId,
|
|
11
10
|
description: analysis.description,
|
|
12
11
|
collaborators: analysis.collaborators,
|
|
13
12
|
donation: analysis.donation === null ? null : { account: analysis.donation.account },
|
|
14
|
-
badges: analysis.badges.map((badge) => ({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
color: badge.color,
|
|
19
|
-
})),
|
|
20
|
-
usage: analysis.usage !== null
|
|
21
|
-
? {
|
|
22
|
-
packageName: analysis.usage.packageName,
|
|
23
|
-
commands: sortByName(analysis.usage.commands.map((command) => ({
|
|
24
|
-
name: command.name,
|
|
25
|
-
command: command.command,
|
|
26
|
-
}))),
|
|
27
|
-
}
|
|
28
|
-
: null,
|
|
29
|
-
readmeUsageDescription: analysis.readmeUsageDescription,
|
|
30
|
-
readmeUsage: analysis.readmeUsage
|
|
31
|
-
.map((usageEntry) => ({
|
|
32
|
-
title: usageEntry.title,
|
|
33
|
-
description: usageEntry.description,
|
|
34
|
-
language: usageEntry.language,
|
|
35
|
-
code: usageEntry.code,
|
|
36
|
-
sourcePath: usageEntry.sourcePath,
|
|
37
|
-
}))
|
|
13
|
+
badges: analysis.badges.map((badge) => ({ ...badge })),
|
|
14
|
+
usage: { ...analysis.usage },
|
|
15
|
+
usageEntries: analysis.usageEntries
|
|
16
|
+
.map((entry) => ({ ...entry, see: [...entry.see], security: [...entry.security] }))
|
|
38
17
|
.sort((left, right) => left.sourcePath.localeCompare(right.sourcePath)),
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
language: analysis.readmeConfig.language,
|
|
49
|
-
code: analysis.readmeConfig.code,
|
|
50
|
-
sourcePath: analysis.readmeConfig.sourcePath,
|
|
51
|
-
}
|
|
52
|
-
: null,
|
|
53
|
-
config: analysis.config !== null
|
|
54
|
-
? {
|
|
55
|
-
exportName: analysis.config.exportName,
|
|
56
|
-
isReadme: analysis.config.isReadme,
|
|
18
|
+
exampleCount: analysis.exampleCount,
|
|
19
|
+
findings: analysis.findings.map((finding) => ({ ...finding })),
|
|
20
|
+
readmeConfig: analysis.readmeConfig === null ? null : { ...analysis.readmeConfig },
|
|
21
|
+
config: analysis.config === null
|
|
22
|
+
? null
|
|
23
|
+
: {
|
|
24
|
+
...analysis.config,
|
|
25
|
+
see: [...analysis.config.see],
|
|
26
|
+
security: [...analysis.config.security],
|
|
57
27
|
members: analysis.config.members,
|
|
58
|
-
}
|
|
59
|
-
: null,
|
|
28
|
+
},
|
|
60
29
|
entrypoints: [...analysis.entrypoints].sort((a, b) => a.localeCompare(b)),
|
|
61
30
|
modules: [...analysis.modules]
|
|
62
31
|
.map((module) => ({
|
|
63
|
-
|
|
64
|
-
isEntrypoint: module.isEntrypoint,
|
|
32
|
+
...module,
|
|
65
33
|
dependencies: [...module.dependencies].sort((a, b) => a.localeCompare(b)),
|
|
66
34
|
exports: [...module.exports].sort((a, b) => a.localeCompare(b)),
|
|
67
35
|
}))
|
|
68
36
|
.sort((left, right) => left.path.localeCompare(right.path)),
|
|
69
|
-
exports,
|
|
37
|
+
exports: sortByName([...exportsByName.values()]),
|
|
70
38
|
components: sortByName(analysis.components.map((component) => mapComponent(component, exportsByName.get(component.name)))),
|
|
71
39
|
sourceFunctions: analysis.sourceFunctions.map((sourceFunction) => ({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
line: sourceFunction.sourceLocation.line,
|
|
77
|
-
column: sourceFunction.sourceLocation.column,
|
|
78
|
-
},
|
|
40
|
+
...sourceFunction,
|
|
41
|
+
see: [...sourceFunction.see],
|
|
42
|
+
security: [...sourceFunction.security],
|
|
43
|
+
sourceLocation: { ...sourceFunction.sourceLocation },
|
|
79
44
|
})),
|
|
80
|
-
sequenceScenarios: sortByName(analysis.sequenceScenarios.map((scenario) => ({
|
|
81
|
-
kind: scenario.kind,
|
|
82
|
-
name: scenario.name,
|
|
83
|
-
sourcePath: scenario.sourcePath,
|
|
84
|
-
symbolName: scenario.symbolName,
|
|
85
|
-
description: scenario.description,
|
|
86
|
-
isReadme: scenario.isReadme,
|
|
87
|
-
}))),
|
|
45
|
+
sequenceScenarios: sortByName(analysis.sequenceScenarios.map((scenario) => ({ ...scenario }))),
|
|
88
46
|
graphs: {
|
|
89
47
|
imports: [...analysis.graphs.imports],
|
|
90
48
|
calls: [...analysis.graphs.calls],
|
|
@@ -99,39 +57,24 @@ export function buildModel(analysis) {
|
|
|
99
57
|
function mapExport(item, exportNames) {
|
|
100
58
|
return {
|
|
101
59
|
name: item.name,
|
|
60
|
+
title: item.title,
|
|
102
61
|
description: item.description,
|
|
103
62
|
isReadme: item.isReadme,
|
|
104
|
-
|
|
63
|
+
see: [...item.see],
|
|
64
|
+
security: [...item.security],
|
|
105
65
|
kind: item.kind,
|
|
106
66
|
modulePath: item.modulePath,
|
|
107
|
-
sourceLocation: {
|
|
108
|
-
filePath: item.sourceLocation.filePath,
|
|
109
|
-
line: item.sourceLocation.line,
|
|
110
|
-
column: item.sourceLocation.column,
|
|
111
|
-
},
|
|
67
|
+
sourceLocation: { ...item.sourceLocation },
|
|
112
68
|
exportPaths: [...item.exportPaths].sort((a, b) => a.localeCompare(b)),
|
|
113
69
|
relatedSymbols: item.relatedSymbols
|
|
114
70
|
.filter((symbol) => exportNames.has(symbol))
|
|
115
71
|
.sort((a, b) => a.localeCompare(b)),
|
|
116
72
|
signatures: item.signatures.map((signature) => ({
|
|
117
|
-
|
|
118
|
-
parameters: sortByName(signature.parameters.map((parameter) => ({
|
|
119
|
-
name: parameter.name,
|
|
120
|
-
type: parameter.type,
|
|
121
|
-
required: parameter.required,
|
|
122
|
-
description: parameter.description,
|
|
123
|
-
}))),
|
|
124
|
-
returnType: signature.returnType,
|
|
125
|
-
returnDescription: signature.returnDescription,
|
|
73
|
+
...signature,
|
|
74
|
+
parameters: sortByName(signature.parameters.map((parameter) => ({ ...parameter }))),
|
|
126
75
|
})),
|
|
127
76
|
members: sortByName(item.members.map((member) => ({
|
|
128
|
-
|
|
129
|
-
kind: member.kind,
|
|
130
|
-
type: member.type,
|
|
131
|
-
required: member.required,
|
|
132
|
-
description: member.description,
|
|
133
|
-
defaultValue: member.defaultValue,
|
|
134
|
-
inheritedFrom: member.inheritedFrom,
|
|
77
|
+
...member,
|
|
135
78
|
children: member.children,
|
|
136
79
|
}))),
|
|
137
80
|
structuredRows: item.structuredRows.map((row) => ({ values: { ...row.values } })),
|
|
@@ -145,25 +88,16 @@ function mapComponent(component, exportModel) {
|
|
|
145
88
|
name: component.name,
|
|
146
89
|
description: component.description,
|
|
147
90
|
isReadme: component.isReadme,
|
|
148
|
-
|
|
91
|
+
see: [...component.see],
|
|
92
|
+
security: [...component.security],
|
|
149
93
|
modulePath: component.modulePath,
|
|
150
|
-
sourceLocation: {
|
|
151
|
-
filePath: component.sourceLocation.filePath,
|
|
152
|
-
line: component.sourceLocation.line,
|
|
153
|
-
column: component.sourceLocation.column,
|
|
154
|
-
},
|
|
94
|
+
sourceLocation: { ...component.sourceLocation },
|
|
155
95
|
exportPaths: exportModel?.exportPaths ?? [...component.exportPaths].sort((a, b) => a.localeCompare(b)),
|
|
156
|
-
props: sortByName(component.props.map((prop) => ({
|
|
157
|
-
name: prop.name,
|
|
158
|
-
type: prop.type,
|
|
159
|
-
required: prop.required,
|
|
160
|
-
defaultValue: prop.defaultValue,
|
|
161
|
-
description: prop.description,
|
|
162
|
-
}))),
|
|
96
|
+
props: sortByName(component.props.map((prop) => ({ ...prop }))),
|
|
163
97
|
};
|
|
164
98
|
}
|
|
165
99
|
/***
|
|
166
|
-
* Returns a copy of items sorted by their
|
|
100
|
+
* Returns a copy of items sorted by their name property.
|
|
167
101
|
*/
|
|
168
102
|
function sortByName(items) {
|
|
169
103
|
return [...items].sort((a, b) => a.name.localeCompare(b.name));
|
package/dist/model/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PolicySeverity } from '@ankhorage/policy/status';
|
|
1
2
|
/***
|
|
2
3
|
* Serializable model consumed by renderers and writers.
|
|
3
4
|
*/
|
|
@@ -8,10 +9,10 @@ export interface DocumentationModel {
|
|
|
8
9
|
collaborators: true | null;
|
|
9
10
|
donation: DonationModel | null;
|
|
10
11
|
badges: GeneratedBadge[];
|
|
11
|
-
usage: UsageModel
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
usage: UsageModel;
|
|
13
|
+
usageEntries: UsageEntryModel[];
|
|
14
|
+
exampleCount: number;
|
|
15
|
+
findings: DocumentationFindingModel[];
|
|
15
16
|
readmeConfig: ReadmeConfigModel | null;
|
|
16
17
|
config: ConfigModel | null;
|
|
17
18
|
entrypoints: string[];
|
|
@@ -33,39 +34,47 @@ export interface GeneratedBadge {
|
|
|
33
34
|
}
|
|
34
35
|
interface UsageModel {
|
|
35
36
|
packageName: string;
|
|
36
|
-
commands: UsageCommandModel[];
|
|
37
|
-
}
|
|
38
|
-
interface UsageCommandModel {
|
|
39
|
-
name: string;
|
|
40
37
|
command: string;
|
|
41
38
|
}
|
|
42
|
-
interface
|
|
39
|
+
interface UsageEntryModel {
|
|
40
|
+
area: 'cli' | 'examples';
|
|
43
41
|
title: string | null;
|
|
44
42
|
description: string | null;
|
|
45
43
|
language: string;
|
|
46
44
|
code: string;
|
|
47
45
|
sourcePath: string;
|
|
46
|
+
isReadme: boolean;
|
|
47
|
+
see: string[];
|
|
48
|
+
security: string[];
|
|
48
49
|
}
|
|
49
|
-
interface
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
interface DocumentationFindingModel {
|
|
51
|
+
ruleId: string;
|
|
52
|
+
severity: PolicySeverity;
|
|
53
|
+
message: string;
|
|
54
|
+
sourcePath: string | null;
|
|
55
|
+
line: number | null;
|
|
52
56
|
}
|
|
53
57
|
interface ReadmeConfigModel {
|
|
54
|
-
description: string | null;
|
|
55
58
|
language: string;
|
|
56
59
|
code: string;
|
|
57
60
|
sourcePath: string;
|
|
58
61
|
}
|
|
59
62
|
interface ConfigModel {
|
|
60
63
|
exportName: string;
|
|
64
|
+
title: string | null;
|
|
65
|
+
description: string | null;
|
|
61
66
|
isReadme: boolean;
|
|
67
|
+
see: string[];
|
|
68
|
+
security: string[];
|
|
62
69
|
members: ConfigMemberModel[];
|
|
63
70
|
}
|
|
64
71
|
export interface ExportModel {
|
|
65
72
|
name: string;
|
|
73
|
+
title: string | null;
|
|
66
74
|
description: string | null;
|
|
67
75
|
isReadme: boolean;
|
|
68
|
-
|
|
76
|
+
see: string[];
|
|
77
|
+
security: string[];
|
|
69
78
|
kind: ExportKind;
|
|
70
79
|
modulePath: string;
|
|
71
80
|
sourceLocation: SourceLocationModel;
|
|
@@ -80,7 +89,8 @@ export interface ComponentModel {
|
|
|
80
89
|
name: string;
|
|
81
90
|
description: string | null;
|
|
82
91
|
isReadme: boolean;
|
|
83
|
-
|
|
92
|
+
see: string[];
|
|
93
|
+
security: string[];
|
|
84
94
|
modulePath: string;
|
|
85
95
|
sourceLocation: SourceLocationModel;
|
|
86
96
|
exportPaths: string[];
|
|
@@ -89,6 +99,8 @@ export interface ComponentModel {
|
|
|
89
99
|
interface SourceFunctionModel {
|
|
90
100
|
name: string;
|
|
91
101
|
description: string | null;
|
|
102
|
+
see: string[];
|
|
103
|
+
security: string[];
|
|
92
104
|
sourceLocation: SourceLocationModel;
|
|
93
105
|
}
|
|
94
106
|
export interface SequenceScenarioModel {
|
|
@@ -105,11 +117,6 @@ export interface ModuleModel {
|
|
|
105
117
|
dependencies: string[];
|
|
106
118
|
exports: string[];
|
|
107
119
|
}
|
|
108
|
-
interface ExampleModel {
|
|
109
|
-
title: string | null;
|
|
110
|
-
language: string | null;
|
|
111
|
-
code: string;
|
|
112
|
-
}
|
|
113
120
|
interface SourceLocationModel {
|
|
114
121
|
filePath: string;
|
|
115
122
|
line: number;
|
package/dist/paths/policy.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { uniqueSortedStrings } from '@ankhorage/utility/array';
|
|
2
|
+
import { toFileStem } from '../toFileStem.js';
|
|
1
3
|
const MAX_SEQUENCE_CALL_EDGES = 12;
|
|
2
4
|
const MAX_SEQUENCE_PARTICIPANTS = 8;
|
|
3
5
|
const MAX_BIN_SEQUENCE_PARTICIPANTS = 12;
|
|
@@ -173,7 +175,7 @@ function groupCallsBySource(callEdges) {
|
|
|
173
175
|
return grouped;
|
|
174
176
|
}
|
|
175
177
|
function collectSequenceParticipants(callEdges) {
|
|
176
|
-
return
|
|
178
|
+
return uniqueSortedStrings(callEdges.flatMap((edge) => [edge.fromSymbol, edge.toSymbol]));
|
|
177
179
|
}
|
|
178
180
|
function getCallEdgeKey(edge) {
|
|
179
181
|
return `${edge.fromSymbol}->${edge.toSymbol}@${edge.sourcePath}:${edge.callExpression}`;
|
|
@@ -190,16 +192,6 @@ function renderFallbackEdge(modules, prefix) {
|
|
|
190
192
|
return ` ${toMermaidId(`${prefix}-${previous.path}`)} -.-> ${toMermaidId(`${prefix}-${module.path}`)}`;
|
|
191
193
|
});
|
|
192
194
|
}
|
|
193
|
-
function uniqueSorted(values) {
|
|
194
|
-
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
195
|
-
}
|
|
196
|
-
function toFileStem(value) {
|
|
197
|
-
return value
|
|
198
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
199
|
-
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
200
|
-
.replace(/^-+|-+$/g, '')
|
|
201
|
-
.toLowerCase();
|
|
202
|
-
}
|
|
203
195
|
function toMermaidId(value) {
|
|
204
196
|
return value.replace(/[^A-Za-z0-9_]/g, '_');
|
|
205
197
|
}
|
|
@@ -4,7 +4,6 @@ import { slugifyAscii } from '@ankhorage/utility/string';
|
|
|
4
4
|
*/
|
|
5
5
|
export function renderHtml({ diagrams, model, }) {
|
|
6
6
|
const sourceAreas = getSourceAreas(model);
|
|
7
|
-
const cliScenarios = getReadmeCliScenarios(model);
|
|
8
7
|
const exportsByModule = groupBy(model.exports, (item) => item.modulePath);
|
|
9
8
|
return {
|
|
10
9
|
indexHtml: `<!doctype html>
|
|
@@ -141,7 +140,7 @@ export function renderHtml({ diagrams, model, }) {
|
|
|
141
140
|
</aside>
|
|
142
141
|
<main class="content">
|
|
143
142
|
<section id="view-home" class="view" data-view="home">
|
|
144
|
-
${renderHomeView(model, diagrams,
|
|
143
|
+
${renderHomeView(model, diagrams, exportsByModule)}
|
|
145
144
|
</section>
|
|
146
145
|
${sourceAreas.map(renderSourceAreaView).join('')}
|
|
147
146
|
</main>
|
|
@@ -197,7 +196,7 @@ export function renderHtml({ diagrams, model, }) {
|
|
|
197
196
|
/***
|
|
198
197
|
* Renders the Home view that keeps public API and package-level information together.
|
|
199
198
|
*/
|
|
200
|
-
function renderHomeView(model, diagrams,
|
|
199
|
+
function renderHomeView(model, diagrams, exportsByModule) {
|
|
201
200
|
return `
|
|
202
201
|
<section class="panel">
|
|
203
202
|
<h2>Package overview</h2>
|
|
@@ -212,7 +211,8 @@ function renderHomeView(model, diagrams, cliScenarios, exportsByModule) {
|
|
|
212
211
|
${model.entrypoints.map((entrypoint) => `<li><code>${escapeHtml(entrypoint)}</code></li>`).join('')}
|
|
213
212
|
</ul>
|
|
214
213
|
</section>
|
|
215
|
-
${
|
|
214
|
+
${renderUsagePanel(model)}
|
|
215
|
+
${renderFindingsPanel(model)}
|
|
216
216
|
<section class="panel">
|
|
217
217
|
<h2>Modules</h2>
|
|
218
218
|
${model.modules.map(renderModuleCard).join('')}
|
|
@@ -237,36 +237,62 @@ function renderHomeView(model, diagrams, cliScenarios, exportsByModule) {
|
|
|
237
237
|
</section>`;
|
|
238
238
|
}
|
|
239
239
|
/***
|
|
240
|
-
* Renders
|
|
240
|
+
* Renders complete CLI and programmatic usage documentation.
|
|
241
241
|
*/
|
|
242
|
-
function
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
model.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
return '';
|
|
259
|
-
return `<article class="item" data-search="${escapeAttribute([scenario.name, scenario.description ?? ''].join(' '))}">
|
|
260
|
-
<h3>${escapeHtml(scenario.name)}</h3>
|
|
261
|
-
${scenario.description === null ? '' : `<p>${escapeHtml(scenario.description)}</p>`}
|
|
262
|
-
${diagram === undefined ? '' : renderDiagramCard(diagram)}
|
|
263
|
-
</article>`;
|
|
264
|
-
})
|
|
265
|
-
.join('')}
|
|
242
|
+
function renderUsagePanel(model) {
|
|
243
|
+
return `<section class="panel" data-search="${escapeAttribute([
|
|
244
|
+
'usage',
|
|
245
|
+
model.usage.command,
|
|
246
|
+
...model.usageEntries.flatMap((entry) => [
|
|
247
|
+
entry.title ?? '',
|
|
248
|
+
entry.description ?? '',
|
|
249
|
+
entry.sourcePath,
|
|
250
|
+
]),
|
|
251
|
+
].join(' '))}">
|
|
252
|
+
<h2>Usage</h2>
|
|
253
|
+
<article class="item">
|
|
254
|
+
<h3>CLI</h3>
|
|
255
|
+
<pre>${escapeHtml(model.usage.command)}</pre>
|
|
256
|
+
</article>
|
|
257
|
+
${model.usageEntries.map(renderUsageEntry).join('')}
|
|
266
258
|
</section>`;
|
|
267
259
|
}
|
|
268
260
|
/***
|
|
269
|
-
* Renders one source
|
|
261
|
+
* Renders one source-backed usage entry in the complete documentation app.
|
|
262
|
+
*/
|
|
263
|
+
function renderUsageEntry(entry) {
|
|
264
|
+
return `<article class="item" data-search="${escapeAttribute([
|
|
265
|
+
entry.title ?? '',
|
|
266
|
+
entry.description ?? '',
|
|
267
|
+
entry.sourcePath,
|
|
268
|
+
...entry.see,
|
|
269
|
+
...entry.security,
|
|
270
|
+
].join(' '))}">
|
|
271
|
+
<h3>${escapeHtml(entry.title ?? 'Usage')}</h3>
|
|
272
|
+
<p class="muted"><code>${escapeHtml(entry.sourcePath)}</code></p>
|
|
273
|
+
${entry.description === null ? '' : `<p>${escapeHtml(entry.description)}</p>`}
|
|
274
|
+
${renderReferenceMetadata(entry)}
|
|
275
|
+
<pre>${escapeHtml(entry.code)}</pre>
|
|
276
|
+
</article>`;
|
|
277
|
+
}
|
|
278
|
+
/***
|
|
279
|
+
* Renders policy findings so complete docs expose the same evidence consumed by Doctor.
|
|
280
|
+
*/
|
|
281
|
+
function renderFindingsPanel(model) {
|
|
282
|
+
if (model.findings.length === 0) {
|
|
283
|
+
return '<section class="panel"><h2>Policy</h2><p>No documentation findings.</p></section>';
|
|
284
|
+
}
|
|
285
|
+
return `<section class="panel">
|
|
286
|
+
<h2>Policy findings</h2>
|
|
287
|
+
${model.findings
|
|
288
|
+
.map((finding) => `<article class="item" data-search="${escapeAttribute([finding.ruleId, finding.severity, finding.message, finding.sourcePath ?? ''].join(' '))}">
|
|
289
|
+
<h3>${escapeHtml(finding.ruleId)}</h3>
|
|
290
|
+
<p><strong>${escapeHtml(finding.severity)}</strong> — ${escapeHtml(finding.message)}</p>
|
|
291
|
+
${finding.sourcePath === null ? '' : `<p class="muted"><code>${escapeHtml(finding.sourcePath)}${finding.line === null ? '' : `:${finding.line}`}</code></p>`}
|
|
292
|
+
</article>`)
|
|
293
|
+
.join('')}
|
|
294
|
+
</section>`;
|
|
295
|
+
}
|
|
270
296
|
/***
|
|
271
297
|
* Renders one source file entry in the left navigation.
|
|
272
298
|
*/
|
|
@@ -293,10 +319,17 @@ function renderSourceAreaView(area) {
|
|
|
293
319
|
* Renders one source function card in a source-area view.
|
|
294
320
|
*/
|
|
295
321
|
function renderSourceFunctionCard(item) {
|
|
296
|
-
return `<article class="item" data-search="${escapeAttribute([
|
|
322
|
+
return `<article class="item" data-search="${escapeAttribute([
|
|
323
|
+
item.name,
|
|
324
|
+
item.sourceLocation.filePath,
|
|
325
|
+
item.description ?? '',
|
|
326
|
+
...item.see,
|
|
327
|
+
...item.security,
|
|
328
|
+
].join(' '))}">
|
|
297
329
|
<h3>${escapeHtml(item.name)}</h3>
|
|
298
330
|
<p class="muted"><code>${escapeHtml(item.sourceLocation.filePath)}:${item.sourceLocation.line}:${item.sourceLocation.column}</code></p>
|
|
299
331
|
${item.description === null ? '<p class="empty">No description available.</p>' : `<p>${escapeHtml(item.description)}</p>`}
|
|
332
|
+
${renderReferenceMetadata(item)}
|
|
300
333
|
</article>`;
|
|
301
334
|
}
|
|
302
335
|
/***
|
|
@@ -307,22 +340,6 @@ function getSourceAreas(model) {
|
|
|
307
340
|
.map(([path, functions]) => ({ path, functions }))
|
|
308
341
|
.sort((left, right) => left.path.localeCompare(right.path));
|
|
309
342
|
}
|
|
310
|
-
/***
|
|
311
|
-
* Selects bin scenarios that should be shown on the Home page.
|
|
312
|
-
*/
|
|
313
|
-
function getReadmeCliScenarios(model) {
|
|
314
|
-
if (model.readmeCli === null)
|
|
315
|
-
return [];
|
|
316
|
-
return model.sequenceScenarios.filter((scenario) => scenario.kind === 'bin');
|
|
317
|
-
}
|
|
318
|
-
/***
|
|
319
|
-
* Finds the generated Mermaid artifact for a sequence scenario.
|
|
320
|
-
/***
|
|
321
|
-
* Finds the generated Mermaid artifact for a sequence scenario.
|
|
322
|
-
*/
|
|
323
|
-
function findScenarioDiagram(diagrams, scenario) {
|
|
324
|
-
return diagrams.find((diagram) => diagram.path === `diagrams/sequences/${toFileStem(scenario.name)}.mmd`);
|
|
325
|
-
}
|
|
326
343
|
/***
|
|
327
344
|
* Renders module metadata on the Home page.
|
|
328
345
|
*/
|
|
@@ -340,15 +357,20 @@ function renderModuleCard(module) {
|
|
|
340
357
|
function renderExportCard(item) {
|
|
341
358
|
return `<article class="item" id="symbol-${slugifyAscii(item.name)}" data-search="${escapeAttribute([
|
|
342
359
|
item.name,
|
|
360
|
+
item.title ?? '',
|
|
343
361
|
item.kind,
|
|
344
362
|
item.modulePath,
|
|
345
363
|
item.description ?? '',
|
|
364
|
+
...item.see,
|
|
365
|
+
...item.security,
|
|
346
366
|
...item.relatedSymbols,
|
|
347
367
|
...item.signatures.map((signature) => signature.label),
|
|
348
368
|
].join(' '))}">
|
|
349
|
-
<h4>${escapeHtml(item.name)}</h4>
|
|
369
|
+
<h4>${escapeHtml(item.title ?? item.name)}</h4>
|
|
370
|
+
${item.title === null || item.title === item.name ? '' : `<p class="muted">Symbol: <code>${escapeHtml(item.name)}</code></p>`}
|
|
350
371
|
<p class="muted">${escapeHtml(item.kind)} • <code>${escapeHtml(item.sourceLocation.filePath)}:${item.sourceLocation.line}:${item.sourceLocation.column}</code></p>
|
|
351
372
|
${item.description ? `<p>${escapeHtml(item.description)}</p>` : ''}
|
|
373
|
+
${renderReferenceMetadata(item)}
|
|
352
374
|
<p><strong>Export paths:</strong> ${renderInlineCodeList(item.exportPaths)}</p>
|
|
353
375
|
<div><strong>Related symbols:</strong> ${item.relatedSymbols.length > 0 ? renderChipList(item.relatedSymbols) : '<span class="empty">None</span>'}</div>
|
|
354
376
|
${item.signatures.length > 0 ? renderSignatureBlock(item) : ''}
|
|
@@ -409,11 +431,14 @@ function renderComponentCard(component) {
|
|
|
409
431
|
component.name,
|
|
410
432
|
component.modulePath,
|
|
411
433
|
component.description ?? '',
|
|
434
|
+
...component.see,
|
|
435
|
+
...component.security,
|
|
412
436
|
...component.props.map((prop) => `${prop.name} ${prop.type}`),
|
|
413
437
|
].join(' '))}">
|
|
414
438
|
<h3>${escapeHtml(component.name)}</h3>
|
|
415
439
|
<p class="muted"><code>${escapeHtml(component.sourceLocation.filePath)}:${component.sourceLocation.line}:${component.sourceLocation.column}</code></p>
|
|
416
440
|
${component.description ? `<p>${escapeHtml(component.description)}</p>` : ''}
|
|
441
|
+
${renderReferenceMetadata(component)}
|
|
417
442
|
<p><strong>Export paths:</strong> ${renderInlineCodeList(component.exportPaths)}</p>
|
|
418
443
|
<table>
|
|
419
444
|
<thead><tr><th>Prop</th><th>Type</th><th>Required</th><th>Description</th></tr></thead>
|
|
@@ -444,6 +469,22 @@ function renderDiagramCard(diagram) {
|
|
|
444
469
|
</details>
|
|
445
470
|
</article>`;
|
|
446
471
|
}
|
|
472
|
+
/***
|
|
473
|
+
* Renders validated external references and security-test evidence.
|
|
474
|
+
*/
|
|
475
|
+
function renderReferenceMetadata(metadata) {
|
|
476
|
+
const see = metadata.see.length === 0
|
|
477
|
+
? ''
|
|
478
|
+
: `<p><strong>See also:</strong> ${metadata.see
|
|
479
|
+
.map((url) => `<a href="${escapeAttribute(url)}" rel="noreferrer">${escapeHtml(url)}</a>`)
|
|
480
|
+
.join(', ')}</p>`;
|
|
481
|
+
const security = metadata.security.length === 0
|
|
482
|
+
? ''
|
|
483
|
+
: `<p><strong>Security tests:</strong> ${metadata.security
|
|
484
|
+
.map((reference) => `<code>${escapeHtml(reference)}</code>`)
|
|
485
|
+
.join(', ')}</p>`;
|
|
486
|
+
return `${see}${security}`;
|
|
487
|
+
}
|
|
447
488
|
/***
|
|
448
489
|
* Renders an inline comma-separated list of code values.
|
|
449
490
|
*/
|
|
@@ -478,16 +519,6 @@ function groupBy(items, key) {
|
|
|
478
519
|
}
|
|
479
520
|
return new Map([...groups.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
480
521
|
}
|
|
481
|
-
/***
|
|
482
|
-
* Converts a scenario name to the generated Mermaid file stem.
|
|
483
|
-
*/
|
|
484
|
-
function toFileStem(value) {
|
|
485
|
-
return value
|
|
486
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
487
|
-
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
488
|
-
.replace(/^-+|-+$/g, '')
|
|
489
|
-
.toLowerCase();
|
|
490
|
-
}
|
|
491
522
|
/***
|
|
492
523
|
* Escapes user-controlled text for safe HTML rendering.
|
|
493
524
|
*/
|