@ankhorage/paradox 0.0.10 → 0.1.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 +12 -0
- package/README.md +82 -19
- package/dist/analyze/analyze.d.ts +0 -3
- package/dist/analyze/analyze.js +6 -11
- package/dist/analyze/components.js +3 -0
- package/dist/analyze/exports.d.ts +1 -0
- package/dist/analyze/exports.js +16 -3
- package/dist/analyze/sequenceScenarios.d.ts +14 -0
- package/dist/analyze/sequenceScenarios.js +174 -0
- package/dist/analyze/types.d.ts +20 -0
- package/dist/analyze/utils/parseParadoxComment.d.ts +7 -0
- package/dist/analyze/utils/parseParadoxComment.js +61 -14
- package/dist/cli.js +9 -0
- package/dist/config/defineParadoxConfig.d.ts +2 -0
- package/dist/config/defineParadoxConfig.js +2 -0
- package/dist/config/types.d.ts +1 -0
- package/dist/model/buildModel.d.ts +19 -0
- package/dist/model/buildModel.js +14 -0
- package/dist/model/types.d.ts +20 -0
- package/dist/render/renderers/diagrams.js +90 -28
- package/dist/render/renderers/markdown.js +258 -47
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3a7c982: Render sequence diagrams from call flow instead of import topology.
|
|
8
|
+
|
|
9
|
+
## 0.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- dd41897: Generate compact README docs from `@readme` symbols.
|
|
14
|
+
|
|
3
15
|
## 0.0.10
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,15 +1,55 @@
|
|
|
1
|
+
<!-- markdownlint-disable MD013 MD033 -->
|
|
2
|
+
<!-- This file is generated by Paradox. Do not edit manually. -->
|
|
3
|
+
|
|
1
4
|
# @ankhorage/paradox
|
|
2
5
|
|
|
3
|
-
         
|
|
4
7
|
|
|
5
8
|
Deterministic documentation generator for TypeScript packages.
|
|
6
9
|
|
|
7
|
-
##
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bunx @ankhorage/paradox
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## CLI
|
|
17
|
+
|
|
18
|
+
### paradox
|
|
19
|
+
|
|
20
|
+
Runs the Paradox CLI.
|
|
21
|
+
|
|
22
|
+
The command discovers the nearest Paradox config, resolves the package and output roots,
|
|
23
|
+
analyzes the package, builds the documentation model, renders all documentation artifacts,
|
|
24
|
+
and writes them to the configured output directory.
|
|
8
25
|
|
|
9
26
|
```bash
|
|
10
27
|
bunx @ankhorage/paradox
|
|
11
28
|
```
|
|
12
29
|
|
|
30
|
+
## Documentation Tags
|
|
31
|
+
|
|
32
|
+
<details>
|
|
33
|
+
<summary>@readme</summary>
|
|
34
|
+
|
|
35
|
+
Includes a documentation block or exported symbol in README output.
|
|
36
|
+
|
|
37
|
+
</details>
|
|
38
|
+
|
|
39
|
+
<details>
|
|
40
|
+
<summary>@config</summary>
|
|
41
|
+
|
|
42
|
+
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.
|
|
43
|
+
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
<details>
|
|
47
|
+
<summary>@example</summary>
|
|
48
|
+
|
|
49
|
+
Adds a titled fenced code example to the generated documentation for a symbol.
|
|
50
|
+
|
|
51
|
+
</details>
|
|
52
|
+
|
|
13
53
|
## Configuration
|
|
14
54
|
|
|
15
55
|
Create a `paradox.config.ts` file:
|
|
@@ -22,14 +62,17 @@ export default defineParadoxConfig({
|
|
|
22
62
|
});
|
|
23
63
|
```
|
|
24
64
|
|
|
25
|
-
|
|
65
|
+
<details>
|
|
66
|
+
<summary>Configuration options</summary>
|
|
26
67
|
|
|
27
68
|
| Field | Type | Required | Default | Description |
|
|
28
69
|
| ------- | --------------------------------------------------------- | -------- | ------- | ----------- |
|
|
29
|
-
| mode | `'safe' \| 'write' \| undefined` | no |
|
|
30
|
-
| docs | `{ title?: string; description?: string; } \| undefined` | no |
|
|
31
|
-
| package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no |
|
|
32
|
-
| output | `{ dir?: string; } \| undefined` | no |
|
|
70
|
+
| mode | `'safe' \| 'write' \| undefined` | no | — | |
|
|
71
|
+
| docs | `{ title?: string; description?: string; } \| undefined` | no | — | |
|
|
72
|
+
| package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no | — | |
|
|
73
|
+
| output | `{ dir?: string; } \| undefined` | no | — | |
|
|
74
|
+
|
|
75
|
+
</details>
|
|
33
76
|
|
|
34
77
|
## Generated documentation
|
|
35
78
|
|
|
@@ -39,10 +82,12 @@ export default defineParadoxConfig({
|
|
|
39
82
|
- [Architecture overview](./paradox/diagrams/architecture-overview.mmd)
|
|
40
83
|
- [Module relationships](./paradox/diagrams/module-relationships.mmd)
|
|
41
84
|
- [Export graph](./paradox/diagrams/export-graph.mmd)
|
|
42
|
-
- [Entrypoint sequence](./paradox/diagrams/entrypoint-sequence.mmd)
|
|
43
85
|
|
|
44
86
|
## Architecture preview
|
|
45
87
|
|
|
88
|
+
<details>
|
|
89
|
+
<summary>Architecture overview</summary>
|
|
90
|
+
|
|
46
91
|
```mermaid
|
|
47
92
|
graph TD
|
|
48
93
|
package__ankhorage_paradox["@ankhorage/paradox"]
|
|
@@ -58,6 +103,7 @@ graph TD
|
|
|
58
103
|
module_src_analyze_analyze_ts --> module_src_analyze_semantic_createTypeScriptProgram_ts
|
|
59
104
|
module_src_analyze_analyze_ts --> module_src_analyze_semantic_exports_ts
|
|
60
105
|
module_src_analyze_analyze_ts --> module_src_analyze_semantic_graphs_ts
|
|
106
|
+
module_src_analyze_analyze_ts --> module_src_analyze_sequenceScenarios_ts
|
|
61
107
|
module_src_analyze_analyze_ts --> module_src_analyze_types_ts
|
|
62
108
|
module_src_analyze_analyze_ts --> module_src_analyze_usage_ts
|
|
63
109
|
module_src_analyze_analyze_ts --> module_src_config_types_ts
|
|
@@ -133,6 +179,13 @@ graph TD
|
|
|
133
179
|
package__ankhorage_paradox -.-> module_src_analyze_semantic_tagRegistry_ts
|
|
134
180
|
module_src_analyze_semantic_utils_ts["src/analyze/semantic/utils.ts"]
|
|
135
181
|
package__ankhorage_paradox -.-> module_src_analyze_semantic_utils_ts
|
|
182
|
+
module_src_analyze_sequenceScenarios_ts["src/analyze/sequenceScenarios.ts"]
|
|
183
|
+
package__ankhorage_paradox -.-> module_src_analyze_sequenceScenarios_ts
|
|
184
|
+
module_src_analyze_sequenceScenarios_ts --> module_src_analyze_semantic_utils_ts
|
|
185
|
+
module_src_analyze_sequenceScenarios_ts --> module_src_analyze_types_ts
|
|
186
|
+
module_src_analyze_sequenceScenarios_ts --> module_src_analyze_usage_ts
|
|
187
|
+
module_src_analyze_sequenceScenarios_ts --> module_src_analyze_utils_getParadoxComment_ts
|
|
188
|
+
module_src_analyze_sequenceScenarios_ts --> module_src_analyze_utils_parseParadoxComment_ts
|
|
136
189
|
module_src_analyze_types_ts["src/analyze/types.ts"]
|
|
137
190
|
package__ankhorage_paradox -.-> module_src_analyze_types_ts
|
|
138
191
|
module_src_analyze_usage_ts["src/analyze/usage.ts"]
|
|
@@ -212,6 +265,8 @@ graph TD
|
|
|
212
265
|
module_src_write_write_ts --> module_src_render_types_ts
|
|
213
266
|
```
|
|
214
267
|
|
|
268
|
+
</details>
|
|
269
|
+
|
|
215
270
|
## Path resolution
|
|
216
271
|
|
|
217
272
|
- Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).
|
|
@@ -223,21 +278,29 @@ graph TD
|
|
|
223
278
|
|
|
224
279
|
## Public API
|
|
225
280
|
|
|
226
|
-
###
|
|
281
|
+
### Configuration
|
|
282
|
+
|
|
283
|
+
<details>
|
|
284
|
+
<summary>defineParadoxConfig</summary>
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
defineParadoxConfig(config: ParadoxConfig) => ParadoxConfig
|
|
288
|
+
```
|
|
227
289
|
|
|
228
290
|
Defines a Paradox configuration object without changing its shape.
|
|
229
291
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
292
|
+
Module: `src/config/defineParadoxConfig.ts`
|
|
293
|
+
Source: `src/config/defineParadoxConfig.ts:8:1`
|
|
294
|
+
Related symbols: `ParadoxConfig`
|
|
295
|
+
|
|
296
|
+
</details>
|
|
235
297
|
|
|
236
|
-
|
|
298
|
+
<details>
|
|
299
|
+
<summary>ParadoxConfig</summary>
|
|
237
300
|
|
|
238
301
|
Configuration for running Paradox.
|
|
239
302
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
303
|
+
Module: `src/config/types.ts`
|
|
304
|
+
Source: `src/config/types.ts:7:1`
|
|
305
|
+
|
|
306
|
+
</details>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { ParadoxConfig } from '../config/types.js';
|
|
2
2
|
import type { AnalysisResult } from './types.js';
|
|
3
|
-
/***
|
|
4
|
-
* Runs the source analysis pipeline for a configured package.
|
|
5
|
-
*/
|
|
6
3
|
export declare function analyze(config: ParadoxConfig, runtime: {
|
|
7
4
|
packageRoot: string;
|
|
8
5
|
}): Promise<AnalysisResult>;
|
package/dist/analyze/analyze.js
CHANGED
|
@@ -8,10 +8,8 @@ import { createProject } from './project.js';
|
|
|
8
8
|
import { createTypeScriptProgram } from './semantic/createTypeScriptProgram.js';
|
|
9
9
|
import { collectTypeMembers, resolveTypeReference } from './semantic/exports.js';
|
|
10
10
|
import { collectCallGraph, collectComponentCompositionGraph, collectImportGraph, } from './semantic/graphs.js';
|
|
11
|
+
import { analyzeSequenceScenarios } from './sequenceScenarios.js';
|
|
11
12
|
import { createUsageFromPackageJson } from './usage.js';
|
|
12
|
-
/***
|
|
13
|
-
* Runs the source analysis pipeline for a configured package.
|
|
14
|
-
*/
|
|
15
13
|
export async function analyze(config, runtime) {
|
|
16
14
|
const root = runtime.packageRoot;
|
|
17
15
|
const pkg = await readPackageJson(root);
|
|
@@ -20,15 +18,10 @@ export async function analyze(config, runtime) {
|
|
|
20
18
|
const project = createProject(root);
|
|
21
19
|
const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
|
|
22
20
|
const program = createTypeScriptProgram({ root, entrypoints, project });
|
|
23
|
-
const { config: configMetadata, exports } = analyzeExports(project, {
|
|
24
|
-
root,
|
|
25
|
-
entrypoints,
|
|
26
|
-
});
|
|
21
|
+
const { config: configMetadata, exports } = analyzeExports(project, { root, entrypoints });
|
|
27
22
|
const components = analyzeComponents(exports, { program });
|
|
28
|
-
const modules = analyzeModules(project, {
|
|
29
|
-
|
|
30
|
-
entrypoints,
|
|
31
|
-
});
|
|
23
|
+
const modules = analyzeModules(project, { root, entrypoints });
|
|
24
|
+
const sequenceScenarios = analyzeSequenceScenarios({ project, root, pkg, exports });
|
|
32
25
|
const configExport = configMetadata
|
|
33
26
|
? (exports.find((entry) => entry.name === configMetadata.exportName) ?? null)
|
|
34
27
|
: null;
|
|
@@ -59,10 +52,12 @@ export async function analyze(config, runtime) {
|
|
|
59
52
|
entrypoints: entrypoints.map((entrypoint) => entrypoint.replaceAll('\\', '/')).sort(),
|
|
60
53
|
modules,
|
|
61
54
|
badges,
|
|
55
|
+
sequenceScenarios,
|
|
62
56
|
usage,
|
|
63
57
|
config: configMetadata
|
|
64
58
|
? {
|
|
65
59
|
exportName: configMetadata.exportName,
|
|
60
|
+
isReadme: configMetadata.isReadme,
|
|
66
61
|
members: mapTypeMembers(configMembers),
|
|
67
62
|
}
|
|
68
63
|
: null,
|
|
@@ -17,6 +17,7 @@ export function analyzeComponents(exports, options = {}) {
|
|
|
17
17
|
name: member.name,
|
|
18
18
|
type: member.type,
|
|
19
19
|
required: member.required,
|
|
20
|
+
...(member.defaultValue !== undefined ? { defaultValue: member.defaultValue } : {}),
|
|
20
21
|
description: member.description ?? null,
|
|
21
22
|
})) ?? [];
|
|
22
23
|
const propsType = getComponentPropsType(e.node);
|
|
@@ -25,6 +26,8 @@ export function analyzeComponents(exports, options = {}) {
|
|
|
25
26
|
components.push({
|
|
26
27
|
name: e.name,
|
|
27
28
|
description: e.description,
|
|
29
|
+
isReadme: e.isReadme,
|
|
30
|
+
examples: e.examples,
|
|
28
31
|
modulePath: e.modulePath,
|
|
29
32
|
sourceLocation: e.sourceLocation,
|
|
30
33
|
exportPaths: e.exportPaths,
|
package/dist/analyze/exports.js
CHANGED
|
@@ -19,13 +19,12 @@ export function analyzeExports(project, options) {
|
|
|
19
19
|
continue;
|
|
20
20
|
}
|
|
21
21
|
const rawComment = getParadoxComment(decl);
|
|
22
|
-
const parsed = rawComment
|
|
23
|
-
? parseParadoxComment(rawComment)
|
|
24
|
-
: { description: null, isConfig: false, params: {}, returns: null };
|
|
22
|
+
const parsed = rawComment ? parseParadoxComment(rawComment) : createEmptyMetadata();
|
|
25
23
|
const name = resolved.getName();
|
|
26
24
|
if (parsed.isConfig) {
|
|
27
25
|
config = {
|
|
28
26
|
exportName: name,
|
|
27
|
+
isReadme: parsed.isReadme,
|
|
29
28
|
};
|
|
30
29
|
}
|
|
31
30
|
const metadata = getExportMetadata({
|
|
@@ -40,6 +39,8 @@ export function analyzeExports(project, options) {
|
|
|
40
39
|
? {
|
|
41
40
|
...existing,
|
|
42
41
|
description: existing.description ?? parsed.description,
|
|
42
|
+
isReadme: existing.isReadme || parsed.isReadme,
|
|
43
|
+
examples: existing.examples.length > 0 ? existing.examples : parsed.examples,
|
|
43
44
|
exportPaths: uniqueSorted([...existing.exportPaths, ...metadata.exportPaths]),
|
|
44
45
|
relatedSymbols: uniqueSorted([
|
|
45
46
|
...existing.relatedSymbols,
|
|
@@ -52,6 +53,8 @@ export function analyzeExports(project, options) {
|
|
|
52
53
|
name,
|
|
53
54
|
node: decl,
|
|
54
55
|
description: parsed.description,
|
|
56
|
+
isReadme: parsed.isReadme,
|
|
57
|
+
examples: parsed.examples,
|
|
55
58
|
kind: inferKind(decl),
|
|
56
59
|
...metadata,
|
|
57
60
|
});
|
|
@@ -87,3 +90,13 @@ function uniqueSorted(values) {
|
|
|
87
90
|
function toPosixPath(path) {
|
|
88
91
|
return path.replaceAll('\\', '/');
|
|
89
92
|
}
|
|
93
|
+
function createEmptyMetadata() {
|
|
94
|
+
return {
|
|
95
|
+
description: null,
|
|
96
|
+
isConfig: false,
|
|
97
|
+
isReadme: false,
|
|
98
|
+
examples: [],
|
|
99
|
+
params: {},
|
|
100
|
+
returns: null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Project } from 'ts-morph';
|
|
2
|
+
import type { AnalysisExport, AnalysisSequenceScenario } from './types.js';
|
|
3
|
+
import type { PackageJsonModel } from './usage.js';
|
|
4
|
+
interface AnalyzeSequenceScenariosOptions {
|
|
5
|
+
project: Project;
|
|
6
|
+
root: string;
|
|
7
|
+
pkg: PackageJsonModel;
|
|
8
|
+
exports: readonly AnalysisExport[];
|
|
9
|
+
}
|
|
10
|
+
/***
|
|
11
|
+
* Finds scenario roots that can be rendered as sequence diagrams.
|
|
12
|
+
*/
|
|
13
|
+
export declare function analyzeSequenceScenarios({ project, root, pkg, exports, }: AnalyzeSequenceScenariosOptions): AnalysisSequenceScenario[];
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { isAbsolute, join, normalize } from 'node:path';
|
|
2
|
+
import { Node as MorphNode, } from 'ts-morph';
|
|
3
|
+
import { relativeToRoot, toPosixPath } from './semantic/utils.js';
|
|
4
|
+
import { getParadoxComment } from './utils/getParadoxComment.js';
|
|
5
|
+
import { parseParadoxComment } from './utils/parseParadoxComment.js';
|
|
6
|
+
/***
|
|
7
|
+
* Finds scenario roots that can be rendered as sequence diagrams.
|
|
8
|
+
*/
|
|
9
|
+
export function analyzeSequenceScenarios({ project, root, pkg, exports, }) {
|
|
10
|
+
return uniqueScenarios([
|
|
11
|
+
...analyzeBinSequenceScenarios(project, root, pkg),
|
|
12
|
+
...analyzeExportSequenceScenarios(exports),
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
15
|
+
function analyzeBinSequenceScenarios(project, root, pkg) {
|
|
16
|
+
return getBinEntries(pkg).flatMap((entry) => {
|
|
17
|
+
const sourceFile = resolveBinSourceFile(project, root, entry.targetPath);
|
|
18
|
+
if (!sourceFile)
|
|
19
|
+
return [];
|
|
20
|
+
const callableRoot = findTopLevelInvokedLocalCallable(sourceFile);
|
|
21
|
+
if (callableRoot === null)
|
|
22
|
+
return [];
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
kind: 'bin',
|
|
26
|
+
name: entry.name,
|
|
27
|
+
sourcePath: relativeToRoot(root, sourceFile.getFilePath()),
|
|
28
|
+
symbolName: callableRoot.symbolName,
|
|
29
|
+
description: callableRoot.description,
|
|
30
|
+
isReadme: callableRoot.isReadme,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function analyzeExportSequenceScenarios(exports) {
|
|
36
|
+
return exports.flatMap((entry) => {
|
|
37
|
+
if (entry.signatures.length === 0)
|
|
38
|
+
return [];
|
|
39
|
+
return [
|
|
40
|
+
{
|
|
41
|
+
kind: 'export',
|
|
42
|
+
name: entry.name,
|
|
43
|
+
sourcePath: entry.modulePath,
|
|
44
|
+
symbolName: entry.name,
|
|
45
|
+
description: entry.description,
|
|
46
|
+
isReadme: entry.isReadme,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function getBinEntries(pkg) {
|
|
52
|
+
if (pkg.bin == null)
|
|
53
|
+
return [];
|
|
54
|
+
if (typeof pkg.bin === 'string') {
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
name: getPackageBaseName(pkg.name),
|
|
58
|
+
targetPath: pkg.bin,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
return Object.entries(pkg.bin)
|
|
63
|
+
.map(([name, targetPath]) => ({ name, targetPath }))
|
|
64
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
65
|
+
}
|
|
66
|
+
function resolveBinSourceFile(project, root, targetPath) {
|
|
67
|
+
for (const candidate of getBinSourceCandidates(targetPath)) {
|
|
68
|
+
const sourceFile = getSourceFileByRelativePath(project, root, candidate);
|
|
69
|
+
if (sourceFile)
|
|
70
|
+
return sourceFile;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
function getBinSourceCandidates(targetPath) {
|
|
75
|
+
const normalized = toPosixPath(targetPath).replace(/^\.\//, '');
|
|
76
|
+
const candidates = [];
|
|
77
|
+
if (/^src\/.*\.tsx?$/.test(normalized)) {
|
|
78
|
+
candidates.push(normalized);
|
|
79
|
+
}
|
|
80
|
+
if (/^dist\/.*\.jsx?$/.test(normalized)) {
|
|
81
|
+
candidates.push(normalized.replace(/^dist\//, 'src/').replace(/\.jsx?$/, '.ts'));
|
|
82
|
+
candidates.push(normalized.replace(/^dist\//, 'src/').replace(/\.jsx?$/, '.tsx'));
|
|
83
|
+
}
|
|
84
|
+
if (/\.jsx?$/.test(normalized)) {
|
|
85
|
+
candidates.push(normalized.replace(/\.jsx?$/, '.ts'));
|
|
86
|
+
candidates.push(normalized.replace(/\.jsx?$/, '.tsx'));
|
|
87
|
+
}
|
|
88
|
+
return uniqueSorted(candidates);
|
|
89
|
+
}
|
|
90
|
+
function getSourceFileByRelativePath(project, root, relativePath) {
|
|
91
|
+
const absolutePath = normalize(isAbsolute(relativePath) ? relativePath : join(root, relativePath));
|
|
92
|
+
return project.getSourceFile(absolutePath) ?? null;
|
|
93
|
+
}
|
|
94
|
+
function findTopLevelInvokedLocalCallable(sourceFile) {
|
|
95
|
+
const candidates = [];
|
|
96
|
+
sourceFile.forEachDescendant((node) => {
|
|
97
|
+
if (!MorphNode.isCallExpression(node))
|
|
98
|
+
return;
|
|
99
|
+
if (isInsideCallable(node))
|
|
100
|
+
return;
|
|
101
|
+
const callableDeclaration = getLocalFunctionDeclarationForCall(sourceFile, node);
|
|
102
|
+
if (callableDeclaration !== null) {
|
|
103
|
+
candidates.push(callableDeclaration);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const uniqueCandidates = uniqueByFunctionName(candidates);
|
|
107
|
+
const declaration = uniqueCandidates.length === 1 ? uniqueCandidates[0] : undefined;
|
|
108
|
+
if (declaration === undefined)
|
|
109
|
+
return null;
|
|
110
|
+
const parsedComment = getParsedParadoxComment(declaration);
|
|
111
|
+
return {
|
|
112
|
+
symbolName: declaration.getName() ?? 'main',
|
|
113
|
+
description: parsedComment.description,
|
|
114
|
+
isReadme: parsedComment.isReadme,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function getParsedParadoxComment(declaration) {
|
|
118
|
+
const comment = getParadoxComment(declaration);
|
|
119
|
+
if (comment === null) {
|
|
120
|
+
return { description: null, isReadme: false };
|
|
121
|
+
}
|
|
122
|
+
const parsed = parseParadoxComment(comment);
|
|
123
|
+
return {
|
|
124
|
+
description: parsed.description,
|
|
125
|
+
isReadme: parsed.isReadme,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function getLocalFunctionDeclarationForCall(sourceFile, node) {
|
|
129
|
+
const expression = node.getExpression();
|
|
130
|
+
const symbol = expression.getSymbol() ?? expression.getType().getSymbol();
|
|
131
|
+
if (!symbol)
|
|
132
|
+
return null;
|
|
133
|
+
for (const declaration of symbol.getDeclarations()) {
|
|
134
|
+
if (declaration.getSourceFile().getFilePath() !== sourceFile.getFilePath())
|
|
135
|
+
continue;
|
|
136
|
+
if (!MorphNode.isFunctionDeclaration(declaration))
|
|
137
|
+
continue;
|
|
138
|
+
return declaration;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function isInsideCallable(node) {
|
|
143
|
+
return Boolean(node.getFirstAncestor((candidate) => MorphNode.isFunctionDeclaration(candidate) ||
|
|
144
|
+
MorphNode.isMethodDeclaration(candidate) ||
|
|
145
|
+
MorphNode.isFunctionExpression(candidate) ||
|
|
146
|
+
MorphNode.isArrowFunction(candidate) ||
|
|
147
|
+
MorphNode.isClassDeclaration(candidate)));
|
|
148
|
+
}
|
|
149
|
+
function getPackageBaseName(packageName) {
|
|
150
|
+
return packageName.split('/').pop() ?? packageName;
|
|
151
|
+
}
|
|
152
|
+
function uniqueScenarios(scenarios) {
|
|
153
|
+
const seen = new Set();
|
|
154
|
+
return scenarios.filter((scenario) => {
|
|
155
|
+
const key = `${scenario.kind}:${scenario.name}:${scenario.sourcePath}:${scenario.symbolName}`;
|
|
156
|
+
if (seen.has(key))
|
|
157
|
+
return false;
|
|
158
|
+
seen.add(key);
|
|
159
|
+
return true;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function uniqueByFunctionName(declarations) {
|
|
163
|
+
const seen = new Set();
|
|
164
|
+
return declarations.filter((declaration) => {
|
|
165
|
+
const name = declaration.getName();
|
|
166
|
+
if (name === undefined || seen.has(name))
|
|
167
|
+
return false;
|
|
168
|
+
seen.add(name);
|
|
169
|
+
return true;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function uniqueSorted(values) {
|
|
173
|
+
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
174
|
+
}
|
package/dist/analyze/types.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { Node } from 'ts-morph';
|
|
2
|
+
interface AnalysisExample {
|
|
3
|
+
title: string | null;
|
|
4
|
+
language: string | null;
|
|
5
|
+
code: string;
|
|
6
|
+
}
|
|
2
7
|
/***
|
|
3
8
|
* Describes one exported declaration discovered in a package.
|
|
4
9
|
*/
|
|
@@ -33,6 +38,8 @@ export interface AnalysisExport {
|
|
|
33
38
|
name: string;
|
|
34
39
|
node: Node;
|
|
35
40
|
description: string | null;
|
|
41
|
+
isReadme: boolean;
|
|
42
|
+
examples: AnalysisExample[];
|
|
36
43
|
kind: 'function' | 'type' | 'unknown';
|
|
37
44
|
modulePath: string;
|
|
38
45
|
sourceLocation: AnalysisSourceLocation;
|
|
@@ -47,6 +54,8 @@ export interface AnalysisExport {
|
|
|
47
54
|
export interface AnalysisComponent {
|
|
48
55
|
name: string;
|
|
49
56
|
description: string | null;
|
|
57
|
+
isReadme: boolean;
|
|
58
|
+
examples: AnalysisExample[];
|
|
50
59
|
modulePath: string;
|
|
51
60
|
sourceLocation: AnalysisSourceLocation;
|
|
52
61
|
exportPaths: string[];
|
|
@@ -54,6 +63,7 @@ export interface AnalysisComponent {
|
|
|
54
63
|
name: string;
|
|
55
64
|
type: string;
|
|
56
65
|
required: boolean;
|
|
66
|
+
defaultValue?: string;
|
|
57
67
|
description: string | null;
|
|
58
68
|
}[];
|
|
59
69
|
}
|
|
@@ -77,6 +87,14 @@ export interface AnalysisModule {
|
|
|
77
87
|
dependencies: string[];
|
|
78
88
|
exports: string[];
|
|
79
89
|
}
|
|
90
|
+
export interface AnalysisSequenceScenario {
|
|
91
|
+
kind: 'bin' | 'export';
|
|
92
|
+
name: string;
|
|
93
|
+
sourcePath: string;
|
|
94
|
+
symbolName: string;
|
|
95
|
+
description: string | null;
|
|
96
|
+
isReadme: boolean;
|
|
97
|
+
}
|
|
80
98
|
interface AnalysisTypeMember {
|
|
81
99
|
name: string;
|
|
82
100
|
type: string;
|
|
@@ -126,9 +144,11 @@ export interface AnalysisResult {
|
|
|
126
144
|
entrypoints: string[];
|
|
127
145
|
modules: AnalysisModule[];
|
|
128
146
|
badges: AnalysisBadge[];
|
|
147
|
+
sequenceScenarios: AnalysisSequenceScenario[];
|
|
129
148
|
usage: AnalysisUsage | null;
|
|
130
149
|
config: {
|
|
131
150
|
exportName: string;
|
|
151
|
+
isReadme: boolean;
|
|
132
152
|
members: AnalysisTypeMember[];
|
|
133
153
|
} | null;
|
|
134
154
|
graphs: AnalysisGraphs;
|
|
@@ -4,9 +4,16 @@
|
|
|
4
4
|
interface ParsedParadoxComment {
|
|
5
5
|
description: string | null;
|
|
6
6
|
isConfig: boolean;
|
|
7
|
+
isReadme: boolean;
|
|
8
|
+
examples: ParsedExample[];
|
|
7
9
|
params: Record<string, string>;
|
|
8
10
|
returns: string | null;
|
|
9
11
|
}
|
|
12
|
+
interface ParsedExample {
|
|
13
|
+
title: string | null;
|
|
14
|
+
language: string | null;
|
|
15
|
+
code: string;
|
|
16
|
+
}
|
|
10
17
|
/***
|
|
11
18
|
* Parses a Paradox doc comment into structured metadata.
|
|
12
19
|
*/
|
|
@@ -2,20 +2,29 @@
|
|
|
2
2
|
* Parses a Paradox doc comment into structured metadata.
|
|
3
3
|
*/
|
|
4
4
|
export function parseParadoxComment(rawComment) {
|
|
5
|
-
const lines = rawComment
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.split('\n')
|
|
9
|
-
.map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
|
|
5
|
+
const lines = normalizeCommentLines(rawComment);
|
|
6
|
+
const descriptionLines = [];
|
|
7
|
+
const examples = [];
|
|
10
8
|
let isConfig = false;
|
|
9
|
+
let isReadme = false;
|
|
11
10
|
const params = {};
|
|
12
11
|
let returns = null;
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
13
|
+
const line = lines[index] ?? '';
|
|
15
14
|
const trimmed = line.trimStart();
|
|
16
15
|
if (trimmed.startsWith('@config')) {
|
|
17
16
|
isConfig = true;
|
|
18
|
-
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (trimmed.startsWith('@readme')) {
|
|
20
|
+
isReadme = true;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (trimmed.startsWith('@example')) {
|
|
24
|
+
const parsed = parseExample(lines, index);
|
|
25
|
+
examples.push(parsed.example);
|
|
26
|
+
index = parsed.nextIndex;
|
|
27
|
+
continue;
|
|
19
28
|
}
|
|
20
29
|
if (trimmed.startsWith('@param ')) {
|
|
21
30
|
const paramBody = trimmed.slice('@param '.length).trim();
|
|
@@ -23,21 +32,59 @@ export function parseParadoxComment(rawComment) {
|
|
|
23
32
|
if (name) {
|
|
24
33
|
params[name] = descriptionParts.join(' ').trim();
|
|
25
34
|
}
|
|
26
|
-
|
|
35
|
+
continue;
|
|
27
36
|
}
|
|
28
37
|
if (trimmed.startsWith('@returns') || trimmed.startsWith('@return')) {
|
|
29
38
|
const returnBody = trimmed.replace(/^@returns?/, '').trim();
|
|
30
39
|
returns = returnBody.length > 0 ? returnBody : null;
|
|
31
|
-
|
|
40
|
+
continue;
|
|
32
41
|
}
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.trim();
|
|
42
|
+
descriptionLines.push(line);
|
|
43
|
+
}
|
|
44
|
+
const description = descriptionLines.join('\n').trim();
|
|
37
45
|
return {
|
|
38
46
|
description: description.length > 0 ? description : null,
|
|
39
47
|
isConfig,
|
|
48
|
+
isReadme,
|
|
49
|
+
examples,
|
|
40
50
|
params,
|
|
41
51
|
returns,
|
|
42
52
|
};
|
|
43
53
|
}
|
|
54
|
+
function parseExample(lines, startIndex) {
|
|
55
|
+
const header = lines[startIndex]?.trimStart() ?? '';
|
|
56
|
+
const title = header.slice('@example'.length).trim();
|
|
57
|
+
let language = null;
|
|
58
|
+
const codeLines = [];
|
|
59
|
+
let index = startIndex + 1;
|
|
60
|
+
while (index < lines.length && (lines[index] ?? '').trim() === '') {
|
|
61
|
+
index += 1;
|
|
62
|
+
}
|
|
63
|
+
const firstCodeLine = lines[index]?.trim() ?? '';
|
|
64
|
+
if (firstCodeLine.startsWith('```')) {
|
|
65
|
+
language = firstCodeLine.slice('```'.length).trim() || null;
|
|
66
|
+
index += 1;
|
|
67
|
+
while (index < lines.length) {
|
|
68
|
+
const current = lines[index] ?? '';
|
|
69
|
+
if (current.trim() === '```')
|
|
70
|
+
break;
|
|
71
|
+
codeLines.push(current);
|
|
72
|
+
index += 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
example: {
|
|
77
|
+
title: title.length > 0 ? title : null,
|
|
78
|
+
language,
|
|
79
|
+
code: codeLines.join('\n').trimEnd(),
|
|
80
|
+
},
|
|
81
|
+
nextIndex: index,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function normalizeCommentLines(rawComment) {
|
|
85
|
+
return rawComment
|
|
86
|
+
.replace(/^\/\*\*\*/, '')
|
|
87
|
+
.replace(/\*\/$/, '')
|
|
88
|
+
.split('\n')
|
|
89
|
+
.map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
|
|
90
|
+
}
|