@ankhorage/paradox 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/README.md +5 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/docs.yml +45 -0
- package/.prettierignore +1 -0
- package/.prettierrc.js +5 -0
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/bun.lock +663 -0
- package/eslint.config.mjs +12 -0
- package/package.json +36 -0
- package/paradox/components.md +1 -0
- package/paradox/exports.json +57 -0
- package/paradox/exports.md +67 -0
- package/paradox/paradox.json +62 -0
- package/paradox.config.ts +18 -0
- package/src/analyze/analyze.ts +46 -0
- package/src/analyze/components.ts +27 -0
- package/src/analyze/exports.ts +68 -0
- package/src/analyze/project.ts +11 -0
- package/src/analyze/types.ts +36 -0
- package/src/analyze/utils/getComponentPropsType.ts +26 -0
- package/src/analyze/utils/getParadoxComment.ts +24 -0
- package/src/analyze/utils/getPropsFromType.ts +24 -0
- package/src/analyze/utils/isReactComponent.ts +35 -0
- package/src/analyze/utils/parseParadoxComment.ts +23 -0
- package/src/analyze/utils/resolveExportSymbol.ts +8 -0
- package/src/cli.ts +30 -0
- package/src/config/defineParadoxConfig.ts +8 -0
- package/src/config/types.ts +20 -0
- package/src/index.ts +9 -0
- package/src/model/buildModel.ts +25 -0
- package/src/model/types.ts +19 -0
- package/src/render/render.ts +82 -0
- package/src/render/types.ts +10 -0
- package/src/write/write.ts +25 -0
- package/tests/__snapshots__/basic.components.md +10 -0
- package/tests/__snapshots__/basic.exports.md +13 -0
- package/tests/__snapshots__/basic.readme.md +13 -0
- package/tests/analyze.test.ts +60 -0
- package/tests/fixtures/basic/package.json +5 -0
- package/tests/fixtures/basic/src/index.ts +2 -0
- package/tests/fixtures/basic/src/internal.ts +6 -0
- package/tests/fixtures/basic/src/ui.ts +29 -0
- package/tests/fixtures/basic/tsconfig.json +11 -0
- package/tsconfig.eslint.json +16 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
import { createConfig } from '@ankhorage/devtools/eslint';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export default createConfig({
|
|
9
|
+
tsconfigRootDir: __dirname,
|
|
10
|
+
project: ['./tsconfig.eslint.json'],
|
|
11
|
+
files: ['src/**/*.ts', 'tests/*.ts', 'paradox.config.ts'],
|
|
12
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ankhorage/paradox",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Deterministic documentation generator for TypeScript packages.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"paradox": "./src/cli.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc --noEmit",
|
|
15
|
+
"changeset": "changeset",
|
|
16
|
+
"changeset:status": "changeset status --since=origin/main",
|
|
17
|
+
"docs": "bun src/cli.ts",
|
|
18
|
+
"docs:bunx": "bunx @ankhorage/paradox",
|
|
19
|
+
"format": "prettier --write .",
|
|
20
|
+
"format:check": "prettier --check .",
|
|
21
|
+
"lint": "eslint . --max-warnings=0",
|
|
22
|
+
"lint:fix": "eslint . --fix --max-warnings=0",
|
|
23
|
+
"test": "bun test",
|
|
24
|
+
"typecheck": "bun x tsc --noEmit -p tsconfig.json",
|
|
25
|
+
"version-packages": "changeset version"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"ts-morph": "^24.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@ankhorage/devtools": "^1.0.0",
|
|
32
|
+
"@changesets/cli": "^2.31.0",
|
|
33
|
+
"@types/bun": "^1.1.14",
|
|
34
|
+
"typescript": "^5.6.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Components
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "analyze",
|
|
4
|
+
"description": "Runs the source analysis pipeline for a configured package.",
|
|
5
|
+
"kind": "function"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"name": "AnalysisComponent",
|
|
9
|
+
"description": "Describes one React component and its extracted props.",
|
|
10
|
+
"kind": "type"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "AnalysisExport",
|
|
14
|
+
"description": "Describes one exported declaration discovered in a package.",
|
|
15
|
+
"kind": "type"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "AnalysisResult",
|
|
19
|
+
"description": "Complete analysis output used to build the documentation model.",
|
|
20
|
+
"kind": "type"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "defineParadoxConfig",
|
|
24
|
+
"description": "Defines a Paradox configuration object without changing its shape.",
|
|
25
|
+
"kind": "function"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "ParadoxConfig",
|
|
29
|
+
"description": "Configuration for running Paradox against a TypeScript package.",
|
|
30
|
+
"kind": "type"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "buildModel",
|
|
34
|
+
"description": "Converts analysis output into a serializable documentation model.",
|
|
35
|
+
"kind": "function"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "DocumentationModel",
|
|
39
|
+
"description": "Serializable model consumed by renderers and writers.",
|
|
40
|
+
"kind": "type"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "render",
|
|
44
|
+
"description": "Renders the documentation model into README and artifact files.",
|
|
45
|
+
"kind": "function"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "RenderResult",
|
|
49
|
+
"description": "Rendered documentation files ready to be written to disk.",
|
|
50
|
+
"kind": "type"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "write",
|
|
54
|
+
"description": "Writes generated documentation artifacts to the configured output paths.",
|
|
55
|
+
"kind": "function"
|
|
56
|
+
}
|
|
57
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Package Exports
|
|
2
|
+
|
|
3
|
+
## analyze
|
|
4
|
+
|
|
5
|
+
Kind: `function`
|
|
6
|
+
|
|
7
|
+
Runs the source analysis pipeline for a configured package.
|
|
8
|
+
|
|
9
|
+
## AnalysisComponent
|
|
10
|
+
|
|
11
|
+
Kind: `type`
|
|
12
|
+
|
|
13
|
+
Describes one React component and its extracted props.
|
|
14
|
+
|
|
15
|
+
## AnalysisExport
|
|
16
|
+
|
|
17
|
+
Kind: `type`
|
|
18
|
+
|
|
19
|
+
Describes one exported declaration discovered in a package.
|
|
20
|
+
|
|
21
|
+
## AnalysisResult
|
|
22
|
+
|
|
23
|
+
Kind: `type`
|
|
24
|
+
|
|
25
|
+
Complete analysis output used to build the documentation model.
|
|
26
|
+
|
|
27
|
+
## defineParadoxConfig
|
|
28
|
+
|
|
29
|
+
Kind: `function`
|
|
30
|
+
|
|
31
|
+
Defines a Paradox configuration object without changing its shape.
|
|
32
|
+
|
|
33
|
+
## ParadoxConfig
|
|
34
|
+
|
|
35
|
+
Kind: `type`
|
|
36
|
+
|
|
37
|
+
Configuration for running Paradox against a TypeScript package.
|
|
38
|
+
|
|
39
|
+
## buildModel
|
|
40
|
+
|
|
41
|
+
Kind: `function`
|
|
42
|
+
|
|
43
|
+
Converts analysis output into a serializable documentation model.
|
|
44
|
+
|
|
45
|
+
## DocumentationModel
|
|
46
|
+
|
|
47
|
+
Kind: `type`
|
|
48
|
+
|
|
49
|
+
Serializable model consumed by renderers and writers.
|
|
50
|
+
|
|
51
|
+
## render
|
|
52
|
+
|
|
53
|
+
Kind: `function`
|
|
54
|
+
|
|
55
|
+
Renders the documentation model into README and artifact files.
|
|
56
|
+
|
|
57
|
+
## RenderResult
|
|
58
|
+
|
|
59
|
+
Kind: `type`
|
|
60
|
+
|
|
61
|
+
Rendered documentation files ready to be written to disk.
|
|
62
|
+
|
|
63
|
+
## write
|
|
64
|
+
|
|
65
|
+
Kind: `function`
|
|
66
|
+
|
|
67
|
+
Writes generated documentation artifacts to the configured output paths.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packageName": "@ankhorage/paradox",
|
|
3
|
+
"description": "Deterministic documentation generator for TypeScript packages.",
|
|
4
|
+
"exports": [
|
|
5
|
+
{
|
|
6
|
+
"name": "analyze",
|
|
7
|
+
"description": "Runs the source analysis pipeline for a configured package.",
|
|
8
|
+
"kind": "function"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "AnalysisComponent",
|
|
12
|
+
"description": "Describes one React component and its extracted props.",
|
|
13
|
+
"kind": "type"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "AnalysisExport",
|
|
17
|
+
"description": "Describes one exported declaration discovered in a package.",
|
|
18
|
+
"kind": "type"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "AnalysisResult",
|
|
22
|
+
"description": "Complete analysis output used to build the documentation model.",
|
|
23
|
+
"kind": "type"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "defineParadoxConfig",
|
|
27
|
+
"description": "Defines a Paradox configuration object without changing its shape.",
|
|
28
|
+
"kind": "function"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "ParadoxConfig",
|
|
32
|
+
"description": "Configuration for running Paradox against a TypeScript package.",
|
|
33
|
+
"kind": "type"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "buildModel",
|
|
37
|
+
"description": "Converts analysis output into a serializable documentation model.",
|
|
38
|
+
"kind": "function"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "DocumentationModel",
|
|
42
|
+
"description": "Serializable model consumed by renderers and writers.",
|
|
43
|
+
"kind": "type"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "render",
|
|
47
|
+
"description": "Renders the documentation model into README and artifact files.",
|
|
48
|
+
"kind": "function"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "RenderResult",
|
|
52
|
+
"description": "Rendered documentation files ready to be written to disk.",
|
|
53
|
+
"kind": "type"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "write",
|
|
57
|
+
"description": "Writes generated documentation artifacts to the configured output paths.",
|
|
58
|
+
"kind": "function"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"components": []
|
|
62
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineParadoxConfig } from './src/config/defineParadoxConfig.js';
|
|
2
|
+
|
|
3
|
+
export default defineParadoxConfig({
|
|
4
|
+
mode: 'write',
|
|
5
|
+
|
|
6
|
+
docs: {
|
|
7
|
+
title: '@ankhorage/paradox',
|
|
8
|
+
description: 'Deterministic documentation generator for TypeScript packages.',
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
package: {
|
|
12
|
+
entrypoints: ['src/index.ts'],
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
output: {
|
|
16
|
+
dir: 'paradox',
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { ParadoxConfig } from '../config/types.js';
|
|
5
|
+
import { analyzeComponents } from './components.js';
|
|
6
|
+
import { analyzeExports } from './exports.js';
|
|
7
|
+
import { createProject } from './project.js';
|
|
8
|
+
import type { AnalysisResult } from './types.js';
|
|
9
|
+
|
|
10
|
+
/***
|
|
11
|
+
* Runs the source analysis pipeline for a configured package.
|
|
12
|
+
*/
|
|
13
|
+
export async function analyze(config: ParadoxConfig): Promise<AnalysisResult> {
|
|
14
|
+
const root = config.package?.root ?? process.cwd();
|
|
15
|
+
|
|
16
|
+
const pkg = await readPackageJson(root);
|
|
17
|
+
|
|
18
|
+
const project = createProject(root);
|
|
19
|
+
const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
|
|
20
|
+
|
|
21
|
+
const exports = analyzeExports(project, {
|
|
22
|
+
root,
|
|
23
|
+
entrypoints,
|
|
24
|
+
});
|
|
25
|
+
const components = analyzeComponents(exports);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
packageName: config.docs?.title ?? pkg.name,
|
|
29
|
+
description: config.docs?.description ?? pkg.description ?? null,
|
|
30
|
+
|
|
31
|
+
exports,
|
|
32
|
+
components,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function readPackageJson(root: string): Promise<{
|
|
37
|
+
name: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
}> {
|
|
40
|
+
const raw = await readFile(join(root, 'package.json'), 'utf-8');
|
|
41
|
+
|
|
42
|
+
return JSON.parse(raw) as {
|
|
43
|
+
name: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AnalysisComponent, AnalysisExport } from './types.js';
|
|
2
|
+
import { getComponentPropsType } from './utils/getComponentPropsType.js';
|
|
3
|
+
import { getPropsFromType } from './utils/getPropsFromType.js';
|
|
4
|
+
import { isReactComponent } from './utils/isReactComponent.js';
|
|
5
|
+
|
|
6
|
+
/***
|
|
7
|
+
* Extracts React components and their props from analyzed exports.
|
|
8
|
+
*/
|
|
9
|
+
export function analyzeComponents(exports: readonly AnalysisExport[]): AnalysisComponent[] {
|
|
10
|
+
const components: AnalysisComponent[] = [];
|
|
11
|
+
|
|
12
|
+
for (const e of exports) {
|
|
13
|
+
if (!isReactComponent(e.node)) continue;
|
|
14
|
+
|
|
15
|
+
const propsType = getComponentPropsType(e.node);
|
|
16
|
+
|
|
17
|
+
const props = propsType != null ? getPropsFromType(propsType) : [];
|
|
18
|
+
|
|
19
|
+
components.push({
|
|
20
|
+
name: e.name,
|
|
21
|
+
description: e.description,
|
|
22
|
+
props,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return components;
|
|
27
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isAbsolute, join, normalize } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import type { Node, Project } from 'ts-morph';
|
|
4
|
+
|
|
5
|
+
import type { AnalysisExport } from './types.js';
|
|
6
|
+
import { getParadoxComment } from './utils/getParadoxComment.js';
|
|
7
|
+
import { parseParadoxComment } from './utils/parseParadoxComment.js';
|
|
8
|
+
import { resolveExportSymbol } from './utils/resolveExportSymbol.js';
|
|
9
|
+
|
|
10
|
+
/***
|
|
11
|
+
* Collects exported declarations from configured package entrypoints.
|
|
12
|
+
*/
|
|
13
|
+
export function analyzeExports(
|
|
14
|
+
project: Project,
|
|
15
|
+
options: {
|
|
16
|
+
root: string;
|
|
17
|
+
entrypoints: readonly string[];
|
|
18
|
+
},
|
|
19
|
+
): AnalysisExport[] {
|
|
20
|
+
const exports: AnalysisExport[] = [];
|
|
21
|
+
|
|
22
|
+
for (const sourceFile of getEntryPointSourceFiles(project, options)) {
|
|
23
|
+
const exported = sourceFile.getExportSymbols();
|
|
24
|
+
|
|
25
|
+
for (const symbol of exported) {
|
|
26
|
+
const resolved = resolveExportSymbol(symbol);
|
|
27
|
+
const [decl] = resolved.getDeclarations();
|
|
28
|
+
|
|
29
|
+
const rawComment = getParadoxComment(decl);
|
|
30
|
+
const parsed = rawComment ? parseParadoxComment(rawComment) : { description: null };
|
|
31
|
+
|
|
32
|
+
exports.push({
|
|
33
|
+
name: resolved.getName(),
|
|
34
|
+
node: decl,
|
|
35
|
+
description: parsed.description,
|
|
36
|
+
kind: inferKind(decl),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return exports;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getEntryPointSourceFiles(
|
|
45
|
+
project: Project,
|
|
46
|
+
options: {
|
|
47
|
+
root: string;
|
|
48
|
+
entrypoints: readonly string[];
|
|
49
|
+
},
|
|
50
|
+
) {
|
|
51
|
+
return options.entrypoints
|
|
52
|
+
.map((entrypoint) => {
|
|
53
|
+
const absolutePath = normalize(
|
|
54
|
+
isAbsolute(entrypoint) ? entrypoint : join(options.root, entrypoint),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return project.getSourceFile(
|
|
58
|
+
(sourceFile) => normalize(sourceFile.getFilePath()) === absolutePath,
|
|
59
|
+
);
|
|
60
|
+
})
|
|
61
|
+
.filter((sourceFile) => sourceFile != null);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function inferKind(node: Node): AnalysisExport['kind'] {
|
|
65
|
+
if ('getParameters' in node) return 'function';
|
|
66
|
+
if ('getProperties' in node || 'getMembers' in node) return 'type';
|
|
67
|
+
return 'unknown';
|
|
68
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Project } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/***
|
|
4
|
+
* Creates the ts-morph project used by the analyzer.
|
|
5
|
+
*/
|
|
6
|
+
export function createProject(root: string): Project {
|
|
7
|
+
return new Project({
|
|
8
|
+
tsConfigFilePath: `${root}/tsconfig.json`,
|
|
9
|
+
skipAddingFilesFromTsConfig: false,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Node } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/***
|
|
4
|
+
* Describes one exported declaration discovered in a package.
|
|
5
|
+
*/
|
|
6
|
+
export interface AnalysisExport {
|
|
7
|
+
name: string;
|
|
8
|
+
node: Node;
|
|
9
|
+
description: string | null;
|
|
10
|
+
kind: 'function' | 'type' | 'unknown';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/***
|
|
14
|
+
* Describes one React component and its extracted props.
|
|
15
|
+
*/
|
|
16
|
+
export interface AnalysisComponent {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string | null;
|
|
19
|
+
props: {
|
|
20
|
+
name: string;
|
|
21
|
+
type: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
description: string | null;
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/***
|
|
28
|
+
* Complete analysis output used to build the documentation model.
|
|
29
|
+
*/
|
|
30
|
+
export interface AnalysisResult {
|
|
31
|
+
packageName: string;
|
|
32
|
+
description: string | null;
|
|
33
|
+
|
|
34
|
+
exports: AnalysisExport[];
|
|
35
|
+
components: AnalysisComponent[];
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Node, type Type } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/***
|
|
4
|
+
* Returns the first parameter type for a React component declaration.
|
|
5
|
+
*/
|
|
6
|
+
export function getComponentPropsType(node: Node): Type | null {
|
|
7
|
+
const callSignature = getCallSignature(node);
|
|
8
|
+
const firstParam = callSignature?.getParameters()[0];
|
|
9
|
+
const firstDecl = firstParam?.getDeclarations()[0];
|
|
10
|
+
|
|
11
|
+
if (firstDecl) return firstParam.getTypeAtLocation(firstDecl);
|
|
12
|
+
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getCallSignature(node: Node) {
|
|
17
|
+
if (Node.isFunctionDeclaration(node)) {
|
|
18
|
+
return node.getType().getCallSignatures()[0] ?? null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (Node.isVariableDeclaration(node)) {
|
|
22
|
+
return node.getType().getCallSignatures()[0] ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Node } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/***
|
|
4
|
+
* Reads the nearest Paradox doc comment attached to a declaration.
|
|
5
|
+
*/
|
|
6
|
+
export function getParadoxComment(node: Node): string | null {
|
|
7
|
+
const sourceFile = node.getSourceFile();
|
|
8
|
+
const text = sourceFile.getFullText();
|
|
9
|
+
const nodeStart = node.getStart(false);
|
|
10
|
+
const beforeNode = text.slice(0, nodeStart);
|
|
11
|
+
const commentStart = beforeNode.lastIndexOf('/***');
|
|
12
|
+
|
|
13
|
+
if (commentStart === -1) return null;
|
|
14
|
+
|
|
15
|
+
const commentEnd = text.indexOf('*/', commentStart);
|
|
16
|
+
|
|
17
|
+
if (commentEnd === -1 || commentEnd > nodeStart) return null;
|
|
18
|
+
|
|
19
|
+
const between = text.slice(commentEnd + 2, nodeStart);
|
|
20
|
+
|
|
21
|
+
if (!/^[\s;]*(export\s+)?(default\s+)?$/.test(between)) return null;
|
|
22
|
+
|
|
23
|
+
return text.slice(commentStart, commentEnd + 2);
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Type } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
import type { AnalysisComponent } from '../types.js';
|
|
4
|
+
import { getParadoxComment } from './getParadoxComment.js';
|
|
5
|
+
import { parseParadoxComment } from './parseParadoxComment.js';
|
|
6
|
+
|
|
7
|
+
/***
|
|
8
|
+
* Extracts prop names, types, required flags, and descriptions from a type.
|
|
9
|
+
*/
|
|
10
|
+
export function getPropsFromType(type: Type): AnalysisComponent['props'] {
|
|
11
|
+
return type.getProperties().map((property) => {
|
|
12
|
+
const [declaration] = property.getDeclarations();
|
|
13
|
+
const propertyType = property.getTypeAtLocation(declaration);
|
|
14
|
+
const rawComment = getParadoxComment(declaration);
|
|
15
|
+
const parsed = rawComment ? parseParadoxComment(rawComment) : { description: null };
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
name: property.getName(),
|
|
19
|
+
type: propertyType.getText(declaration),
|
|
20
|
+
required: !property.isOptional(),
|
|
21
|
+
description: parsed.description,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Node } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
/***
|
|
4
|
+
* Detects simple React component declarations by name and return type.
|
|
5
|
+
*/
|
|
6
|
+
export function isReactComponent(node: Node): boolean {
|
|
7
|
+
const name = getDeclarationName(node);
|
|
8
|
+
|
|
9
|
+
if (!name || !/^[A-Z]/.test(name)) return false;
|
|
10
|
+
|
|
11
|
+
const callSignature = getCallSignature(node);
|
|
12
|
+
const returnType = callSignature?.getReturnType().getText() ?? null;
|
|
13
|
+
|
|
14
|
+
if (returnType == null) return false;
|
|
15
|
+
|
|
16
|
+
return /JSX\.Element|ReactElement|ReactNode|Element/.test(returnType);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getDeclarationName(node: Node): string | null {
|
|
20
|
+
if (Node.isFunctionDeclaration(node)) return node.getName() ?? null;
|
|
21
|
+
if (Node.isVariableDeclaration(node)) return node.getName();
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getCallSignature(node: Node) {
|
|
26
|
+
if (Node.isFunctionDeclaration(node)) {
|
|
27
|
+
return node.getType().getCallSignatures()[0] ?? null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (Node.isVariableDeclaration(node)) {
|
|
31
|
+
return node.getType().getCallSignatures()[0] ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Parsed representation of a Paradox doc comment.
|
|
3
|
+
*/
|
|
4
|
+
export interface ParsedParadoxComment {
|
|
5
|
+
description: string | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/***
|
|
9
|
+
* Parses a Paradox doc comment into structured metadata.
|
|
10
|
+
*/
|
|
11
|
+
export function parseParadoxComment(rawComment: string): ParsedParadoxComment {
|
|
12
|
+
const body = rawComment
|
|
13
|
+
.replace(/^\/\*\*\*/, '')
|
|
14
|
+
.replace(/\*\/$/, '')
|
|
15
|
+
.split('\n')
|
|
16
|
+
.map((line) => line.replace(/^\s*\*\s?/, '').trimEnd())
|
|
17
|
+
.join('\n')
|
|
18
|
+
.trim();
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
description: body.length > 0 ? body : null,
|
|
22
|
+
};
|
|
23
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { analyze } from './analyze/analyze.js';
|
|
6
|
+
import type { ParadoxConfig } from './config/types.js';
|
|
7
|
+
import { buildModel } from './model/buildModel.js';
|
|
8
|
+
import { render } from './render/render.js';
|
|
9
|
+
import { write } from './write/write.js';
|
|
10
|
+
|
|
11
|
+
async function main(): Promise<void> {
|
|
12
|
+
const config = await loadConfig(process.cwd());
|
|
13
|
+
const analysis = await analyze(config);
|
|
14
|
+
const model = buildModel(analysis);
|
|
15
|
+
const result = render(model);
|
|
16
|
+
|
|
17
|
+
await write(result, config);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function loadConfig(root: string): Promise<ParadoxConfig> {
|
|
21
|
+
const configUrl = pathToFileURL(join(root, 'paradox.config.ts')).href;
|
|
22
|
+
const mod = (await import(configUrl)) as { default?: ParadoxConfig };
|
|
23
|
+
|
|
24
|
+
return mod.default ?? {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
main().catch((error: unknown) => {
|
|
28
|
+
console.error(error);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/***
|
|
2
|
+
* Configuration for running Paradox against a TypeScript package.
|
|
3
|
+
*/
|
|
4
|
+
export interface ParadoxConfig {
|
|
5
|
+
mode?: 'safe' | 'write';
|
|
6
|
+
|
|
7
|
+
docs?: {
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
package?: {
|
|
13
|
+
root?: string;
|
|
14
|
+
entrypoints?: string[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
output?: {
|
|
18
|
+
dir?: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { analyze } from './analyze/analyze.js';
|
|
2
|
+
export type { AnalysisComponent, AnalysisExport, AnalysisResult } from './analyze/types.js';
|
|
3
|
+
export { defineParadoxConfig } from './config/defineParadoxConfig.js';
|
|
4
|
+
export type { ParadoxConfig } from './config/types.js';
|
|
5
|
+
export { buildModel } from './model/buildModel.js';
|
|
6
|
+
export type { DocumentationModel } from './model/types.js';
|
|
7
|
+
export { render } from './render/render.js';
|
|
8
|
+
export type { RenderResult } from './render/types.js';
|
|
9
|
+
export { write } from './write/write.js';
|