@ankhorage/paradox 0.1.4 → 0.1.6
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 +12 -0
- package/README.md +24 -33
- package/dist/analyze/exports.d.ts +1 -1
- package/dist/analyze/exports.js +24 -0
- package/dist/analyze/types.d.ts +5 -1
- package/dist/analyze/utils/getExportMetadata.d.ts +1 -1
- package/dist/analyze/utils/getExportMetadata.js +143 -2
- package/dist/analyze/utils/getParadoxComment.d.ts +2 -2
- package/dist/analyze/utils/getParadoxComment.js +26 -1
- package/dist/doc-tags/registry.d.ts +39 -0
- package/dist/doc-tags/registry.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/model/buildModel.d.ts +3 -0
- package/dist/model/buildModel.js +1 -0
- package/dist/model/types.d.ts +5 -1
- package/dist/render/renderers/markdown.js +45 -34
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8ba8f5c: Remove the hardcoded Path resolution section from generated README output so consumer documentation only contains analyzed package content.
|
|
8
|
+
|
|
9
|
+
## 0.1.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fd606af: Move documentation tag metadata into a typed registry, stop rendering Paradox tag docs by default in consumer READMEs, and add structured table rendering for documented const array exports.
|
|
14
|
+
|
|
3
15
|
## 0.1.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @ankhorage/paradox
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Deterministic documentation generator for TypeScript packages.
|
|
9
9
|
|
|
@@ -64,29 +64,6 @@ sequenceDiagram
|
|
|
64
64
|
|
|
65
65
|
</details>
|
|
66
66
|
|
|
67
|
-
## Documentation Tags
|
|
68
|
-
|
|
69
|
-
<details>
|
|
70
|
-
<summary>@readme</summary>
|
|
71
|
-
|
|
72
|
-
Includes a documentation block or exported symbol in README output.
|
|
73
|
-
|
|
74
|
-
</details>
|
|
75
|
-
|
|
76
|
-
<details>
|
|
77
|
-
<summary>@config</summary>
|
|
78
|
-
|
|
79
|
-
Marks a type or interface as part of the Paradox configuration model. `@config` alone does not imply README inclusion; use `@config` plus `@readme` for README output.
|
|
80
|
-
|
|
81
|
-
</details>
|
|
82
|
-
|
|
83
|
-
<details>
|
|
84
|
-
<summary>@example</summary>
|
|
85
|
-
|
|
86
|
-
Adds a titled fenced code example to the generated documentation for a symbol.
|
|
87
|
-
|
|
88
|
-
</details>
|
|
89
|
-
|
|
90
67
|
## Configuration
|
|
91
68
|
|
|
92
69
|
Create a `paradox.config.ts` file:
|
|
@@ -119,6 +96,7 @@ export default defineParadoxConfig({
|
|
|
119
96
|
- [Architecture overview](./paradox/diagrams/architecture-overview.mmd)
|
|
120
97
|
- [Module relationships](./paradox/diagrams/module-relationships.mmd)
|
|
121
98
|
- [Export graph](./paradox/diagrams/export-graph.mmd)
|
|
99
|
+
- [isParadoxDocTagName sequence](./paradox/diagrams/sequences/is-paradox-doc-tag-name.mmd)
|
|
122
100
|
- [paradox sequence](./paradox/diagrams/sequences/paradox.mmd)
|
|
123
101
|
|
|
124
102
|
## Architecture preview
|
|
@@ -267,6 +245,8 @@ graph TD
|
|
|
267
245
|
module_src_config_defineParadoxConfig_ts --> module_src_config_types_ts
|
|
268
246
|
module_src_config_types_ts["src/config/types.ts"]
|
|
269
247
|
package__ankhorage_paradox -.-> module_src_config_types_ts
|
|
248
|
+
module_src_doc_tags_registry_ts["src/doc-tags/registry.ts"]
|
|
249
|
+
package__ankhorage_paradox -.-> module_src_doc_tags_registry_ts
|
|
270
250
|
module_src_index_ts["src/index.ts"]
|
|
271
251
|
module_src_model_buildModel_ts["src/model/buildModel.ts"]
|
|
272
252
|
package__ankhorage_paradox -.-> module_src_model_buildModel_ts
|
|
@@ -311,15 +291,6 @@ graph TD
|
|
|
311
291
|
|
|
312
292
|
</details>
|
|
313
293
|
|
|
314
|
-
## Path resolution
|
|
315
|
-
|
|
316
|
-
- Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).
|
|
317
|
-
- Package root: defaults to the directory containing `paradox.config.*`; `package.root` (when relative) resolves relative to that directory.
|
|
318
|
-
- Output directory: defaults to `paradox/`; `output.dir` (when relative) resolves relative to the resolved package root and must stay inside it.
|
|
319
|
-
- Modes:
|
|
320
|
-
- `safe`: writes generated artifacts only under the output directory
|
|
321
|
-
- `write`: additionally updates `<packageRoot>/README.md`
|
|
322
|
-
|
|
323
294
|
## Public API
|
|
324
295
|
|
|
325
296
|
### Config
|
|
@@ -348,3 +319,23 @@ Module: `src/config/types.ts`
|
|
|
348
319
|
Source: `src/config/types.ts:7:1`
|
|
349
320
|
|
|
350
321
|
</details>
|
|
322
|
+
|
|
323
|
+
### Documentation
|
|
324
|
+
|
|
325
|
+
<details>
|
|
326
|
+
<summary>PARADOX_DOC_TAGS</summary>
|
|
327
|
+
|
|
328
|
+
Supported Paradox documentation tags.
|
|
329
|
+
|
|
330
|
+
Paradox supports doc tags inside triple-star documentation comments.
|
|
331
|
+
|
|
332
|
+
| name | syntax | description | applies to | repeatable | handler |
|
|
333
|
+
| --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ---------- | -------------- |
|
|
334
|
+
| `readme` | `@readme` | Includes a documentation block or exported symbol in README output. | block, symbol | no | `markReadme` |
|
|
335
|
+
| `config` | `@config` | Marks a type or interface as part of the Paradox configuration model. @config alone does not imply README inclusion; use @config plus @readme for README output. | interface, type | no | `markConfig` |
|
|
336
|
+
| `example` | `@example` | Adds a titled fenced code example to the generated documentation for a symbol. | symbol | yes | `parseExample` |
|
|
337
|
+
|
|
338
|
+
Module: `src/doc-tags/registry.ts`
|
|
339
|
+
Source: `src/doc-tags/registry.ts:8:14`
|
|
340
|
+
|
|
341
|
+
</details>
|
package/dist/analyze/exports.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, join, normalize, relative } from 'node:path';
|
|
2
|
+
import { Node } from 'ts-morph';
|
|
2
3
|
import { getExportMetadata } from './utils/getExportMetadata.js';
|
|
3
4
|
import { getParadoxComment } from './utils/getParadoxComment.js';
|
|
4
5
|
import { parseParadoxComment } from './utils/parseParadoxComment.js';
|
|
@@ -48,6 +49,9 @@ export function analyzeExports(project, options) {
|
|
|
48
49
|
]),
|
|
49
50
|
signatures: existing.signatures.length > 0 ? existing.signatures : metadata.signatures,
|
|
50
51
|
members: existing.members.length > 0 ? existing.members : metadata.members,
|
|
52
|
+
structuredRows: existing.structuredRows.length > 0
|
|
53
|
+
? existing.structuredRows
|
|
54
|
+
: metadata.structuredRows,
|
|
51
55
|
}
|
|
52
56
|
: {
|
|
53
57
|
name,
|
|
@@ -65,6 +69,9 @@ export function analyzeExports(project, options) {
|
|
|
65
69
|
config,
|
|
66
70
|
};
|
|
67
71
|
}
|
|
72
|
+
/***
|
|
73
|
+
* Resolves configured entrypoint paths to source files in the TypeScript project.
|
|
74
|
+
*/
|
|
68
75
|
function getEntryPointSourceFiles(project, options) {
|
|
69
76
|
return options.entrypoints
|
|
70
77
|
.map((entrypoint) => {
|
|
@@ -73,23 +80,40 @@ function getEntryPointSourceFiles(project, options) {
|
|
|
73
80
|
})
|
|
74
81
|
.filter((sourceFile) => sourceFile != null);
|
|
75
82
|
}
|
|
83
|
+
/***
|
|
84
|
+
* Returns the first declaration for a symbol, or null when none exists.
|
|
85
|
+
*/
|
|
76
86
|
function getFirstDeclaration(declarations) {
|
|
77
87
|
const [declaration = null] = declarations;
|
|
78
88
|
return declaration;
|
|
79
89
|
}
|
|
90
|
+
/***
|
|
91
|
+
* Infers the public export kind from the declaration shape.
|
|
92
|
+
*/
|
|
80
93
|
function inferKind(node) {
|
|
81
94
|
if ('getParameters' in node)
|
|
82
95
|
return 'function';
|
|
83
96
|
if ('getProperties' in node || 'getMembers' in node)
|
|
84
97
|
return 'type';
|
|
98
|
+
if (Node.isVariableDeclaration(node))
|
|
99
|
+
return 'value';
|
|
85
100
|
return 'unknown';
|
|
86
101
|
}
|
|
102
|
+
/***
|
|
103
|
+
* Returns unique string values sorted for deterministic generated output.
|
|
104
|
+
*/
|
|
87
105
|
function uniqueSorted(values) {
|
|
88
106
|
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
89
107
|
}
|
|
108
|
+
/***
|
|
109
|
+
* Normalizes platform-specific path separators for generated documentation output.
|
|
110
|
+
*/
|
|
90
111
|
function toPosixPath(path) {
|
|
91
112
|
return path.replaceAll('\\', '/');
|
|
92
113
|
}
|
|
114
|
+
/***
|
|
115
|
+
* Creates empty documentation metadata for exports without Paradox comments.
|
|
116
|
+
*/
|
|
93
117
|
function createEmptyMetadata() {
|
|
94
118
|
return {
|
|
95
119
|
description: null,
|
package/dist/analyze/types.d.ts
CHANGED
|
@@ -34,19 +34,23 @@ export interface AnalysisMember {
|
|
|
34
34
|
inheritedFrom?: string;
|
|
35
35
|
children?: AnalysisMember[];
|
|
36
36
|
}
|
|
37
|
+
export interface AnalysisStructuredRow {
|
|
38
|
+
values: Record<string, string>;
|
|
39
|
+
}
|
|
37
40
|
export interface AnalysisExport {
|
|
38
41
|
name: string;
|
|
39
42
|
node: Node;
|
|
40
43
|
description: string | null;
|
|
41
44
|
isReadme: boolean;
|
|
42
45
|
examples: AnalysisExample[];
|
|
43
|
-
kind: 'function' | 'type' | 'unknown';
|
|
46
|
+
kind: 'function' | 'type' | 'value' | 'unknown';
|
|
44
47
|
modulePath: string;
|
|
45
48
|
sourceLocation: AnalysisSourceLocation;
|
|
46
49
|
exportPaths: string[];
|
|
47
50
|
relatedSymbols: string[];
|
|
48
51
|
signatures: AnalysisSignature[];
|
|
49
52
|
members: AnalysisMember[];
|
|
53
|
+
structuredRows: AnalysisStructuredRow[];
|
|
50
54
|
}
|
|
51
55
|
/***
|
|
52
56
|
* Describes one React component and its extracted props.
|
|
@@ -9,4 +9,4 @@ export declare function getExportMetadata(options: {
|
|
|
9
9
|
root: string;
|
|
10
10
|
entrypointPath: string;
|
|
11
11
|
symbol: MorphSymbol;
|
|
12
|
-
}): Pick<AnalysisExport, 'exportPaths' | 'members' | 'modulePath' | 'relatedSymbols' | 'signatures' | 'sourceLocation'>;
|
|
12
|
+
}): Pick<AnalysisExport, 'exportPaths' | 'members' | 'modulePath' | 'relatedSymbols' | 'signatures' | 'sourceLocation' | 'structuredRows'>;
|
|
@@ -10,6 +10,7 @@ export function getExportMetadata(options) {
|
|
|
10
10
|
const sourceLocation = getSourceLocation(options.node, options.root);
|
|
11
11
|
const signatures = getSignatures(options.symbol, options.node);
|
|
12
12
|
const members = getMembers(options.node);
|
|
13
|
+
const structuredRows = getStructuredRows(options.node, options.name);
|
|
13
14
|
const relatedSymbols = collectRelatedSymbols(options.name, signatures.flatMap((signature) => [
|
|
14
15
|
...signature.parameters.map((parameter) => parameter.type),
|
|
15
16
|
signature.returnType,
|
|
@@ -21,8 +22,12 @@ export function getExportMetadata(options) {
|
|
|
21
22
|
relatedSymbols,
|
|
22
23
|
signatures,
|
|
23
24
|
members,
|
|
25
|
+
structuredRows,
|
|
24
26
|
};
|
|
25
27
|
}
|
|
28
|
+
/***
|
|
29
|
+
* Resolves the source location for a declaration relative to the package root.
|
|
30
|
+
*/
|
|
26
31
|
function getSourceLocation(node, root) {
|
|
27
32
|
const sourceFile = node.getSourceFile();
|
|
28
33
|
const { column, line } = sourceFile.getLineAndColumnAtPos(node.getStart(false));
|
|
@@ -32,6 +37,9 @@ function getSourceLocation(node, root) {
|
|
|
32
37
|
column,
|
|
33
38
|
};
|
|
34
39
|
}
|
|
40
|
+
/***
|
|
41
|
+
* Extracts callable signatures for exported functions and callable values.
|
|
42
|
+
*/
|
|
35
43
|
function getSignatures(symbol, node) {
|
|
36
44
|
const parsed = readParadoxMetadata(node);
|
|
37
45
|
const signatures = getCallableDeclarations(symbol, node).map((declaration) => getSignature(declaration, parsed.params, parsed.returns));
|
|
@@ -39,6 +47,9 @@ function getSignatures(symbol, node) {
|
|
|
39
47
|
signature.returnType !== null ||
|
|
40
48
|
signature.returnDescription !== null), (signature) => signature.label);
|
|
41
49
|
}
|
|
50
|
+
/***
|
|
51
|
+
* Builds one normalized call signature from a callable declaration.
|
|
52
|
+
*/
|
|
42
53
|
function getSignature(declaration, params, returns) {
|
|
43
54
|
const normalizedParameters = declaration.getParameters().map((parameter) => {
|
|
44
55
|
const parameterDescription = params[parameter.getName()];
|
|
@@ -60,6 +71,9 @@ function getSignature(declaration, params, returns) {
|
|
|
60
71
|
returnDescription: returns,
|
|
61
72
|
};
|
|
62
73
|
}
|
|
74
|
+
/***
|
|
75
|
+
* Extracts members for interface and type literal exports.
|
|
76
|
+
*/
|
|
63
77
|
function getMembers(node) {
|
|
64
78
|
if (getCallableNode(node) !== null)
|
|
65
79
|
return [];
|
|
@@ -74,6 +88,9 @@ function getMembers(node) {
|
|
|
74
88
|
}
|
|
75
89
|
return [];
|
|
76
90
|
}
|
|
91
|
+
/***
|
|
92
|
+
* Converts TypeScript properties into documented member metadata.
|
|
93
|
+
*/
|
|
77
94
|
function getMembersFromProperties(properties) {
|
|
78
95
|
return properties.flatMap((property) => {
|
|
79
96
|
const declaration = getFirstDeclaration(property.getDeclarations());
|
|
@@ -83,7 +100,14 @@ function getMembersFromProperties(properties) {
|
|
|
83
100
|
const rawComment = getParadoxComment(declaration);
|
|
84
101
|
const parsed = rawComment
|
|
85
102
|
? parseParadoxComment(rawComment)
|
|
86
|
-
: {
|
|
103
|
+
: {
|
|
104
|
+
description: null,
|
|
105
|
+
isConfig: false,
|
|
106
|
+
isReadme: false,
|
|
107
|
+
examples: [],
|
|
108
|
+
params: {},
|
|
109
|
+
returns: null,
|
|
110
|
+
};
|
|
87
111
|
return [
|
|
88
112
|
{
|
|
89
113
|
name: property.getName(),
|
|
@@ -95,10 +119,102 @@ function getMembersFromProperties(properties) {
|
|
|
95
119
|
];
|
|
96
120
|
});
|
|
97
121
|
}
|
|
122
|
+
/***
|
|
123
|
+
* Extracts table-like rows from exported const arrays of object literals.
|
|
124
|
+
*/
|
|
125
|
+
function getStructuredRows(node, exportName) {
|
|
126
|
+
const declaration = getVariableDeclaration(node, exportName);
|
|
127
|
+
if (declaration === null)
|
|
128
|
+
return [];
|
|
129
|
+
const initializer = getStructuredArrayLiteral(declaration.getInitializer());
|
|
130
|
+
if (initializer === null)
|
|
131
|
+
return [];
|
|
132
|
+
return initializer.getElements().flatMap((element) => {
|
|
133
|
+
if (!Node.isObjectLiteralExpression(element))
|
|
134
|
+
return [];
|
|
135
|
+
const values = getObjectLiteralValues(element);
|
|
136
|
+
return Object.keys(values).length > 0 ? [{ values }] : [];
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/***
|
|
140
|
+
* Resolves const assertions and returns the array literal used for structured docs.
|
|
141
|
+
*/
|
|
142
|
+
function getStructuredArrayLiteral(node) {
|
|
143
|
+
if (node === undefined)
|
|
144
|
+
return null;
|
|
145
|
+
if (Node.isArrayLiteralExpression(node))
|
|
146
|
+
return node;
|
|
147
|
+
if (Node.isAsExpression(node))
|
|
148
|
+
return getStructuredArrayLiteral(node.getExpression());
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/***
|
|
152
|
+
* Resolves a variable declaration from declaration nodes used by export symbols.
|
|
153
|
+
*/
|
|
154
|
+
function getVariableDeclaration(node, exportName) {
|
|
155
|
+
if (Node.isVariableDeclaration(node))
|
|
156
|
+
return node;
|
|
157
|
+
if (Node.isVariableStatement(node)) {
|
|
158
|
+
return (node
|
|
159
|
+
.getDeclarationList()
|
|
160
|
+
.getDeclarations()
|
|
161
|
+
.find((declaration) => declaration.getName() === exportName) ?? null);
|
|
162
|
+
}
|
|
163
|
+
if (Node.isVariableDeclarationList(node)) {
|
|
164
|
+
return (node.getDeclarations().find((declaration) => declaration.getName() === exportName) ?? null);
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
/***
|
|
169
|
+
* Extracts primitive and string-array values from one object literal.
|
|
170
|
+
*/
|
|
171
|
+
function getObjectLiteralValues(node) {
|
|
172
|
+
const values = {};
|
|
173
|
+
for (const property of node.getProperties()) {
|
|
174
|
+
if (!Node.isPropertyAssignment(property))
|
|
175
|
+
continue;
|
|
176
|
+
const name = property.getName().replace(/^['"]|['"]$/g, '');
|
|
177
|
+
const value = getLiteralValue(property.getInitializer());
|
|
178
|
+
if (value !== null) {
|
|
179
|
+
values[name] = value;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return values;
|
|
183
|
+
}
|
|
184
|
+
/***
|
|
185
|
+
* Converts supported literal expressions into displayable string values.
|
|
186
|
+
*/
|
|
187
|
+
function getLiteralValue(node) {
|
|
188
|
+
if (node === undefined)
|
|
189
|
+
return null;
|
|
190
|
+
if (Node.isStringLiteral(node))
|
|
191
|
+
return node.getLiteralText();
|
|
192
|
+
if (Node.isNoSubstitutionTemplateLiteral(node))
|
|
193
|
+
return node.getLiteralText();
|
|
194
|
+
if (node.getKindName() === 'TrueKeyword')
|
|
195
|
+
return 'true';
|
|
196
|
+
if (node.getKindName() === 'FalseKeyword')
|
|
197
|
+
return 'false';
|
|
198
|
+
if (Node.isNumericLiteral(node))
|
|
199
|
+
return node.getText();
|
|
200
|
+
if (Node.isArrayLiteralExpression(node)) {
|
|
201
|
+
const values = node.getElements().map((element) => getLiteralValue(element));
|
|
202
|
+
if (values.some((value) => value === null))
|
|
203
|
+
return null;
|
|
204
|
+
return values.join(', ');
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
/***
|
|
209
|
+
* Returns the first declaration for a symbol, or null when none exists.
|
|
210
|
+
*/
|
|
98
211
|
function getFirstDeclaration(declarations) {
|
|
99
212
|
const [declaration = null] = declarations;
|
|
100
213
|
return declaration;
|
|
101
214
|
}
|
|
215
|
+
/***
|
|
216
|
+
* Finds callable declarations associated with an export symbol.
|
|
217
|
+
*/
|
|
102
218
|
function getCallableDeclarations(symbol, node) {
|
|
103
219
|
const declarations = symbol
|
|
104
220
|
.getDeclarations()
|
|
@@ -110,6 +226,9 @@ function getCallableDeclarations(symbol, node) {
|
|
|
110
226
|
const callableNode = getCallableNode(node);
|
|
111
227
|
return callableNode !== null ? [callableNode] : [];
|
|
112
228
|
}
|
|
229
|
+
/***
|
|
230
|
+
* Returns the callable node represented by a declaration when one exists.
|
|
231
|
+
*/
|
|
113
232
|
function getCallableNode(node) {
|
|
114
233
|
if (Node.isFunctionDeclaration(node))
|
|
115
234
|
return node;
|
|
@@ -130,15 +249,31 @@ function getCallableNode(node) {
|
|
|
130
249
|
}
|
|
131
250
|
return null;
|
|
132
251
|
}
|
|
252
|
+
/***
|
|
253
|
+
* Checks whether a node is a method declaration or method signature.
|
|
254
|
+
*/
|
|
133
255
|
function isMemberMethodDeclaration(node) {
|
|
134
256
|
return Node.isMethodDeclaration(node) || Node.isMethodSignature(node);
|
|
135
257
|
}
|
|
258
|
+
/***
|
|
259
|
+
* Reads Paradox comment metadata from a declaration.
|
|
260
|
+
*/
|
|
136
261
|
function readParadoxMetadata(node) {
|
|
137
262
|
const rawComment = getParadoxComment(node);
|
|
138
263
|
return rawComment
|
|
139
264
|
? parseParadoxComment(rawComment)
|
|
140
|
-
: {
|
|
265
|
+
: {
|
|
266
|
+
description: null,
|
|
267
|
+
isConfig: false,
|
|
268
|
+
isReadme: false,
|
|
269
|
+
examples: [],
|
|
270
|
+
params: {},
|
|
271
|
+
returns: null,
|
|
272
|
+
};
|
|
141
273
|
}
|
|
274
|
+
/***
|
|
275
|
+
* Finds related exported symbols mentioned in signature and member type text.
|
|
276
|
+
*/
|
|
142
277
|
function collectRelatedSymbols(exportName, ...values) {
|
|
143
278
|
const candidates = values.flatMap((entries) => entries).filter((entry) => entry !== null);
|
|
144
279
|
const related = new Set();
|
|
@@ -152,9 +287,15 @@ function collectRelatedSymbols(exportName, ...values) {
|
|
|
152
287
|
}
|
|
153
288
|
return [...related].sort((left, right) => left.localeCompare(right));
|
|
154
289
|
}
|
|
290
|
+
/***
|
|
291
|
+
* Normalizes platform-specific path separators for generated documentation output.
|
|
292
|
+
*/
|
|
155
293
|
function toPosixPath(path) {
|
|
156
294
|
return path.replaceAll('\\', '/');
|
|
157
295
|
}
|
|
296
|
+
/***
|
|
297
|
+
* Returns unique items by a caller-provided key while preserving first occurrence order.
|
|
298
|
+
*/
|
|
158
299
|
function uniqueBy(items, key) {
|
|
159
300
|
const seen = new Set();
|
|
160
301
|
return items.filter((item) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Node as MorphNode } from 'ts-morph';
|
|
2
2
|
/***
|
|
3
3
|
* Reads the nearest Paradox doc comment attached to a declaration.
|
|
4
4
|
*/
|
|
5
|
-
export declare function getParadoxComment(node:
|
|
5
|
+
export declare function getParadoxComment(node: MorphNode | undefined): string | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Node } from 'ts-morph';
|
|
1
2
|
/***
|
|
2
3
|
* Reads the nearest Paradox doc comment attached to a declaration.
|
|
3
4
|
*/
|
|
@@ -6,7 +7,31 @@ export function getParadoxComment(node) {
|
|
|
6
7
|
return null;
|
|
7
8
|
const sourceFile = node.getSourceFile();
|
|
8
9
|
const text = sourceFile.getFullText();
|
|
9
|
-
const nodeStart
|
|
10
|
+
for (const nodeStart of getCommentTargetStarts(node)) {
|
|
11
|
+
const comment = readCommentBefore(text, nodeStart);
|
|
12
|
+
if (comment !== null)
|
|
13
|
+
return comment;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/***
|
|
18
|
+
* Returns declaration positions that may own a leading Paradox comment.
|
|
19
|
+
*/
|
|
20
|
+
function getCommentTargetStarts(node) {
|
|
21
|
+
const starts = [node.getStart(false)];
|
|
22
|
+
if (Node.isVariableDeclaration(node)) {
|
|
23
|
+
const parent = node.getParent();
|
|
24
|
+
const statement = Node.isVariableDeclarationList(parent) ? parent.getParent() : undefined;
|
|
25
|
+
if (statement !== undefined && Node.isVariableStatement(statement)) {
|
|
26
|
+
starts.unshift(statement.getStart(false));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return starts;
|
|
30
|
+
}
|
|
31
|
+
/***
|
|
32
|
+
* Reads a Paradox doc comment directly before a target position.
|
|
33
|
+
*/
|
|
34
|
+
function readCommentBefore(text, nodeStart) {
|
|
10
35
|
const beforeNode = text.slice(0, nodeStart);
|
|
11
36
|
const commentStart = beforeNode.lastIndexOf('/***');
|
|
12
37
|
if (commentStart === -1)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Supported Paradox documentation tags.
|
|
3
|
+
*
|
|
4
|
+
* Paradox supports doc tags inside triple-star documentation comments.
|
|
5
|
+
*
|
|
6
|
+
* @readme
|
|
7
|
+
*/
|
|
8
|
+
export declare const PARADOX_DOC_TAGS: readonly [{
|
|
9
|
+
readonly name: "readme";
|
|
10
|
+
readonly syntax: "@readme";
|
|
11
|
+
readonly description: "Includes a documentation block or exported symbol in README output.";
|
|
12
|
+
readonly appliesTo: readonly ["block", "symbol"];
|
|
13
|
+
readonly repeatable: false;
|
|
14
|
+
readonly handler: "markReadme";
|
|
15
|
+
}, {
|
|
16
|
+
readonly name: "config";
|
|
17
|
+
readonly syntax: "@config";
|
|
18
|
+
readonly description: "Marks a type or interface as part of the Paradox configuration model. @config alone does not imply README inclusion; use @config plus @readme for README output.";
|
|
19
|
+
readonly appliesTo: readonly ["interface", "type"];
|
|
20
|
+
readonly repeatable: false;
|
|
21
|
+
readonly handler: "markConfig";
|
|
22
|
+
}, {
|
|
23
|
+
readonly name: "example";
|
|
24
|
+
readonly syntax: "@example";
|
|
25
|
+
readonly description: "Adds a titled fenced code example to the generated documentation for a symbol.";
|
|
26
|
+
readonly appliesTo: readonly ["symbol"];
|
|
27
|
+
readonly repeatable: true;
|
|
28
|
+
readonly handler: "parseExample";
|
|
29
|
+
}];
|
|
30
|
+
export type ParadoxDocTagName = (typeof PARADOX_DOC_TAGS)[number]['name'];
|
|
31
|
+
export type ParadoxDocTagHandlerId = (typeof PARADOX_DOC_TAGS)[number]['handler'];
|
|
32
|
+
/***
|
|
33
|
+
* Looks up documentation tag metadata by tag name.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getParadoxDocTag(name: string): (typeof PARADOX_DOC_TAGS)[number] | null;
|
|
36
|
+
/***
|
|
37
|
+
* Checks whether a string is a supported Paradox documentation tag name.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isParadoxDocTagName(name: string): name is ParadoxDocTagName;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Supported Paradox documentation tags.
|
|
3
|
+
*
|
|
4
|
+
* Paradox supports doc tags inside triple-star documentation comments.
|
|
5
|
+
*
|
|
6
|
+
* @readme
|
|
7
|
+
*/
|
|
8
|
+
export const PARADOX_DOC_TAGS = [
|
|
9
|
+
{
|
|
10
|
+
name: 'readme',
|
|
11
|
+
syntax: '@readme',
|
|
12
|
+
description: 'Includes a documentation block or exported symbol in README output.',
|
|
13
|
+
appliesTo: ['block', 'symbol'],
|
|
14
|
+
repeatable: false,
|
|
15
|
+
handler: 'markReadme',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'config',
|
|
19
|
+
syntax: '@config',
|
|
20
|
+
description: 'Marks a type or interface as part of the Paradox configuration model. @config alone does not imply README inclusion; use @config plus @readme for README output.',
|
|
21
|
+
appliesTo: ['interface', 'type'],
|
|
22
|
+
repeatable: false,
|
|
23
|
+
handler: 'markConfig',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'example',
|
|
27
|
+
syntax: '@example',
|
|
28
|
+
description: 'Adds a titled fenced code example to the generated documentation for a symbol.',
|
|
29
|
+
appliesTo: ['symbol'],
|
|
30
|
+
repeatable: true,
|
|
31
|
+
handler: 'parseExample',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
/***
|
|
35
|
+
* Looks up documentation tag metadata by tag name.
|
|
36
|
+
*/
|
|
37
|
+
export function getParadoxDocTag(name) {
|
|
38
|
+
return PARADOX_DOC_TAGS.find((tag) => tag.name === name) ?? null;
|
|
39
|
+
}
|
|
40
|
+
/***
|
|
41
|
+
* Checks whether a string is a supported Paradox documentation tag name.
|
|
42
|
+
*/
|
|
43
|
+
export function isParadoxDocTagName(name) {
|
|
44
|
+
return getParadoxDocTag(name) !== null;
|
|
45
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { defineParadoxConfig } from './config/defineParadoxConfig.js';
|
|
2
2
|
export type { ParadoxConfig } from './config/types.js';
|
|
3
|
+
export type { ParadoxDocTagHandlerId, ParadoxDocTagName } from './doc-tags/registry.js';
|
|
4
|
+
export { getParadoxDocTag, isParadoxDocTagName, PARADOX_DOC_TAGS } from './doc-tags/registry.js';
|
package/dist/index.js
CHANGED
package/dist/model/buildModel.js
CHANGED
package/dist/model/types.d.ts
CHANGED
|
@@ -49,8 +49,9 @@ export interface ExportModel {
|
|
|
49
49
|
relatedSymbols: string[];
|
|
50
50
|
signatures: SignatureModel[];
|
|
51
51
|
members: MemberModel[];
|
|
52
|
+
structuredRows: StructuredRowModel[];
|
|
52
53
|
}
|
|
53
|
-
export type ExportKind = 'function' | 'type' | 'unknown';
|
|
54
|
+
export type ExportKind = 'function' | 'type' | 'value' | 'unknown';
|
|
54
55
|
export interface ComponentModel {
|
|
55
56
|
name: string;
|
|
56
57
|
description: string | null;
|
|
@@ -106,6 +107,9 @@ interface MemberModel {
|
|
|
106
107
|
inheritedFrom?: string;
|
|
107
108
|
children?: MemberModel[];
|
|
108
109
|
}
|
|
110
|
+
interface StructuredRowModel {
|
|
111
|
+
values: Record<string, string>;
|
|
112
|
+
}
|
|
109
113
|
export interface ModuleModel {
|
|
110
114
|
path: string;
|
|
111
115
|
isEntrypoint: boolean;
|
|
@@ -33,13 +33,11 @@ function renderReadme(model, outputDir, badges, diagrams) {
|
|
|
33
33
|
lines.push('```', '');
|
|
34
34
|
}
|
|
35
35
|
renderCliScenarios(lines, model, outputDir, diagrams);
|
|
36
|
-
renderDocumentationTags(lines);
|
|
37
36
|
if (model.config?.isReadme) {
|
|
38
37
|
renderConfiguration(lines, model);
|
|
39
38
|
}
|
|
40
39
|
renderGeneratedDocumentation(lines, outputDir, diagrams);
|
|
41
40
|
renderArchitecturePreview(lines, diagrams);
|
|
42
|
-
renderPathResolution(lines);
|
|
43
41
|
renderReadmeApi(lines, model);
|
|
44
42
|
return `${lines.join('\n').trimEnd()}\n`;
|
|
45
43
|
}
|
|
@@ -73,15 +71,6 @@ function renderCliScenarios(lines, model, outputDir, diagrams) {
|
|
|
73
71
|
function findScenarioDiagram(diagrams, scenario) {
|
|
74
72
|
return diagrams.find((diagram) => diagram.path === `diagrams/sequences/${toFileStem(scenario.name)}.mmd`);
|
|
75
73
|
}
|
|
76
|
-
function renderDocumentationTags(lines) {
|
|
77
|
-
lines.push('## Documentation Tags', '');
|
|
78
|
-
for (const tag of DOCUMENTATION_TAGS) {
|
|
79
|
-
lines.push('<details>');
|
|
80
|
-
lines.push(`<summary>@${tag.name}</summary>`, '');
|
|
81
|
-
lines.push(tag.description, '');
|
|
82
|
-
lines.push('</details>', '');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
74
|
function renderConfiguration(lines, model) {
|
|
86
75
|
const { config } = model;
|
|
87
76
|
if (config === null)
|
|
@@ -138,15 +127,6 @@ function renderArchitecturePreview(lines, diagrams) {
|
|
|
138
127
|
lines.push('</details>', '');
|
|
139
128
|
}
|
|
140
129
|
}
|
|
141
|
-
function renderPathResolution(lines) {
|
|
142
|
-
lines.push('## Path resolution', '');
|
|
143
|
-
lines.push('- Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).');
|
|
144
|
-
lines.push('- Package root: defaults to the directory containing `paradox.config.*`; `package.root` (when relative) resolves relative to that directory.');
|
|
145
|
-
lines.push('- Output directory: defaults to `paradox/`; `output.dir` (when relative) resolves relative to the resolved package root and must stay inside it.');
|
|
146
|
-
lines.push('- Modes:');
|
|
147
|
-
lines.push(' - `safe`: writes generated artifacts only under the output directory');
|
|
148
|
-
lines.push(' - `write`: additionally updates `<packageRoot>/README.md`', '');
|
|
149
|
-
}
|
|
150
130
|
function renderReadmeApi(lines, model) {
|
|
151
131
|
const groups = getReadmeGroups(model);
|
|
152
132
|
if (groups.length === 0)
|
|
@@ -191,6 +171,7 @@ function renderExportAccordion(lines, item) {
|
|
|
191
171
|
lines.push(`<summary>${item.name}</summary>`, '');
|
|
192
172
|
renderSignature(lines, item);
|
|
193
173
|
lines.push(item.description ?? `\`${item.kind}\` export.`, '');
|
|
174
|
+
renderStructuredRows(lines, item);
|
|
194
175
|
renderExamples(lines, item.examples);
|
|
195
176
|
lines.push(`Module: \`${item.modulePath}\``);
|
|
196
177
|
lines.push(`Source: \`${item.sourceLocation.filePath}:${item.sourceLocation.line}:${item.sourceLocation.column}\``);
|
|
@@ -216,6 +197,46 @@ function renderExamples(lines, examples) {
|
|
|
216
197
|
lines.push('```', '');
|
|
217
198
|
}
|
|
218
199
|
}
|
|
200
|
+
function renderStructuredRows(lines, item) {
|
|
201
|
+
if (item.structuredRows.length === 0)
|
|
202
|
+
return;
|
|
203
|
+
const columns = getStructuredColumns(item);
|
|
204
|
+
if (columns.length === 0)
|
|
205
|
+
return;
|
|
206
|
+
lines.push('| ' + columns.map(formatStructuredColumnHeader).join(' | ') + ' |');
|
|
207
|
+
lines.push('| ' + columns.map(() => '---').join(' | ') + ' |');
|
|
208
|
+
for (const row of item.structuredRows) {
|
|
209
|
+
lines.push('| ' +
|
|
210
|
+
columns
|
|
211
|
+
.map((column) => formatStructuredCell(column, row.values[column] ?? ''))
|
|
212
|
+
.join(' | ') +
|
|
213
|
+
' |');
|
|
214
|
+
}
|
|
215
|
+
lines.push('');
|
|
216
|
+
}
|
|
217
|
+
function getStructuredColumns(item) {
|
|
218
|
+
const columns = new Set();
|
|
219
|
+
for (const row of item.structuredRows) {
|
|
220
|
+
for (const column of Object.keys(row.values)) {
|
|
221
|
+
columns.add(column);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return [...columns];
|
|
225
|
+
}
|
|
226
|
+
function formatStructuredColumnHeader(column) {
|
|
227
|
+
return escapeTableCell(column.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase());
|
|
228
|
+
}
|
|
229
|
+
function formatStructuredCell(column, value) {
|
|
230
|
+
const escaped = escapeTableCell(value);
|
|
231
|
+
if (column === 'syntax' || column === 'name' || column === 'handler') {
|
|
232
|
+
return `\`${escaped}\``;
|
|
233
|
+
}
|
|
234
|
+
if (value === 'true')
|
|
235
|
+
return 'yes';
|
|
236
|
+
if (value === 'false')
|
|
237
|
+
return 'no';
|
|
238
|
+
return escaped;
|
|
239
|
+
}
|
|
219
240
|
function getReadmeGroups(model) {
|
|
220
241
|
const exportsByName = new Map(model.exports.map((entry) => [entry.name, entry]));
|
|
221
242
|
const componentNames = new Set(model.components.map((component) => component.name));
|
|
@@ -256,6 +277,8 @@ function getReadmeItemName(item) {
|
|
|
256
277
|
function getReadmeCategory(modulePath, name) {
|
|
257
278
|
if (modulePath.includes('/config/'))
|
|
258
279
|
return 'Config';
|
|
280
|
+
if (modulePath.includes('/doc-tags/'))
|
|
281
|
+
return 'Documentation';
|
|
259
282
|
if (modulePath.includes('/primitives/'))
|
|
260
283
|
return 'Primitives';
|
|
261
284
|
if (modulePath.includes('/components/'))
|
|
@@ -282,6 +305,7 @@ function renderExports(model) {
|
|
|
282
305
|
if (item.description) {
|
|
283
306
|
lines.push(item.description, '');
|
|
284
307
|
}
|
|
308
|
+
renderStructuredRows(lines, item);
|
|
285
309
|
if (item.signatures.length > 0) {
|
|
286
310
|
lines.push('### Signatures', '');
|
|
287
311
|
for (const signature of item.signatures) {
|
|
@@ -365,6 +389,7 @@ function toFileStem(value) {
|
|
|
365
389
|
}
|
|
366
390
|
const CATEGORY_ORDER = [
|
|
367
391
|
'Config',
|
|
392
|
+
'Documentation',
|
|
368
393
|
'Primitives',
|
|
369
394
|
'Components',
|
|
370
395
|
'Patterns',
|
|
@@ -373,17 +398,3 @@ const CATEGORY_ORDER = [
|
|
|
373
398
|
'Utilities',
|
|
374
399
|
'Types',
|
|
375
400
|
];
|
|
376
|
-
const DOCUMENTATION_TAGS = [
|
|
377
|
-
{
|
|
378
|
-
name: 'readme',
|
|
379
|
-
description: 'Includes a documentation block or exported symbol in README output.',
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
name: 'config',
|
|
383
|
-
description: 'Marks a type or interface as part of the Paradox configuration model. `@config` alone does not imply README inclusion; use `@config` plus `@readme` for README output.',
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
name: 'example',
|
|
387
|
-
description: 'Adds a titled fenced code example to the generated documentation for a symbol.',
|
|
388
|
-
},
|
|
389
|
-
];
|