@ankhorage/paradox 0.0.5 → 0.0.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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 1a836cf: Generate a deterministic static documentation app with computed API metadata and Mermaid diagrams.
8
+
9
+ Paradox now emits an offline `paradox/index.html` documentation app, diagram artifacts, richer export metadata, function signature details, parameter and return descriptions, related symbols, and README links to the generated documentation outputs.
10
+
3
11
  ## 0.0.5
4
12
 
5
13
  ### Patch Changes
package/README.md CHANGED
@@ -20,6 +20,124 @@ export default defineParadoxConfig({
20
20
  });
21
21
  ```
22
22
 
23
+ ## Generated documentation
24
+
25
+ - [Interactive documentation app](./paradox/index.html)
26
+ - [Public API reference](./paradox/exports.md)
27
+ - [Component registry](./paradox/components.md)
28
+ - [Architecture overview](./paradox/diagrams/architecture-overview.mmd)
29
+ - [Module relationships](./paradox/diagrams/module-relationships.mmd)
30
+ - [Export graph](./paradox/diagrams/export-graph.mmd)
31
+ - [Entrypoint sequence](./paradox/diagrams/entrypoint-sequence.mmd)
32
+
33
+ ## Architecture preview
34
+
35
+ ```mermaid
36
+ graph TD
37
+ package__ankhorage_paradox["@ankhorage/paradox"]
38
+ entrypoint_src_index_ts["src/index.ts"]
39
+ package__ankhorage_paradox --> entrypoint_src_index_ts
40
+ module_src_analyze_analyze_ts["src/analyze/analyze.ts"]
41
+ package__ankhorage_paradox -.-> module_src_analyze_analyze_ts
42
+ module_src_analyze_analyze_ts --> module_src_analyze_components_ts
43
+ module_src_analyze_analyze_ts --> module_src_analyze_exports_ts
44
+ module_src_analyze_analyze_ts --> module_src_analyze_modules_ts
45
+ module_src_analyze_analyze_ts --> module_src_analyze_project_ts
46
+ module_src_analyze_analyze_ts --> module_src_analyze_types_ts
47
+ module_src_analyze_analyze_ts --> module_src_analyze_usage_ts
48
+ module_src_analyze_analyze_ts --> module_src_config_types_ts
49
+ module_src_analyze_components_ts["src/analyze/components.ts"]
50
+ package__ankhorage_paradox -.-> module_src_analyze_components_ts
51
+ module_src_analyze_components_ts --> module_src_analyze_types_ts
52
+ module_src_analyze_components_ts --> module_src_analyze_utils_getComponentPropsType_ts
53
+ module_src_analyze_components_ts --> module_src_analyze_utils_getPropsFromType_ts
54
+ module_src_analyze_components_ts --> module_src_analyze_utils_isReactComponent_ts
55
+ module_src_analyze_exports_ts["src/analyze/exports.ts"]
56
+ package__ankhorage_paradox -.-> module_src_analyze_exports_ts
57
+ module_src_analyze_exports_ts --> module_src_analyze_types_ts
58
+ module_src_analyze_exports_ts --> module_src_analyze_utils_getExportMetadata_ts
59
+ module_src_analyze_exports_ts --> module_src_analyze_utils_getParadoxComment_ts
60
+ module_src_analyze_exports_ts --> module_src_analyze_utils_parseParadoxComment_ts
61
+ module_src_analyze_exports_ts --> module_src_analyze_utils_resolveExportSymbol_ts
62
+ module_src_analyze_modules_ts["src/analyze/modules.ts"]
63
+ package__ankhorage_paradox -.-> module_src_analyze_modules_ts
64
+ module_src_analyze_modules_ts --> module_src_analyze_types_ts
65
+ module_src_analyze_project_ts["src/analyze/project.ts"]
66
+ package__ankhorage_paradox -.-> module_src_analyze_project_ts
67
+ module_src_analyze_types_ts["src/analyze/types.ts"]
68
+ package__ankhorage_paradox -.-> module_src_analyze_types_ts
69
+ module_src_analyze_usage_ts["src/analyze/usage.ts"]
70
+ package__ankhorage_paradox -.-> module_src_analyze_usage_ts
71
+ module_src_analyze_usage_ts --> module_src_analyze_types_ts
72
+ module_src_analyze_utils_getComponentPropsType_ts["src/analyze/utils/getComponentPropsType.ts"]
73
+ package__ankhorage_paradox -.-> module_src_analyze_utils_getComponentPropsType_ts
74
+ module_src_analyze_utils_getExportMetadata_ts["src/analyze/utils/getExportMetadata.ts"]
75
+ package__ankhorage_paradox -.-> module_src_analyze_utils_getExportMetadata_ts
76
+ module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_types_ts
77
+ module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_utils_getParadoxComment_ts
78
+ module_src_analyze_utils_getExportMetadata_ts --> module_src_analyze_utils_parseParadoxComment_ts
79
+ module_src_analyze_utils_getParadoxComment_ts["src/analyze/utils/getParadoxComment.ts"]
80
+ package__ankhorage_paradox -.-> module_src_analyze_utils_getParadoxComment_ts
81
+ module_src_analyze_utils_getPropsFromType_ts["src/analyze/utils/getPropsFromType.ts"]
82
+ package__ankhorage_paradox -.-> module_src_analyze_utils_getPropsFromType_ts
83
+ module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_types_ts
84
+ module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_utils_getParadoxComment_ts
85
+ module_src_analyze_utils_getPropsFromType_ts --> module_src_analyze_utils_parseParadoxComment_ts
86
+ module_src_analyze_utils_isReactComponent_ts["src/analyze/utils/isReactComponent.ts"]
87
+ package__ankhorage_paradox -.-> module_src_analyze_utils_isReactComponent_ts
88
+ module_src_analyze_utils_parseParadoxComment_ts["src/analyze/utils/parseParadoxComment.ts"]
89
+ package__ankhorage_paradox -.-> module_src_analyze_utils_parseParadoxComment_ts
90
+ module_src_analyze_utils_resolveExportSymbol_ts["src/analyze/utils/resolveExportSymbol.ts"]
91
+ package__ankhorage_paradox -.-> module_src_analyze_utils_resolveExportSymbol_ts
92
+ module_src_cli_ts["src/cli.ts"]
93
+ package__ankhorage_paradox -.-> module_src_cli_ts
94
+ module_src_cli_ts --> module_src_analyze_analyze_ts
95
+ module_src_cli_ts --> module_src_model_buildModel_ts
96
+ module_src_cli_ts --> module_src_paths_policy_ts
97
+ module_src_cli_ts --> module_src_render_render_ts
98
+ module_src_cli_ts --> module_src_write_write_ts
99
+ module_src_config_defineParadoxConfig_ts["src/config/defineParadoxConfig.ts"]
100
+ package__ankhorage_paradox -.-> module_src_config_defineParadoxConfig_ts
101
+ module_src_config_defineParadoxConfig_ts --> module_src_config_types_ts
102
+ module_src_config_types_ts["src/config/types.ts"]
103
+ package__ankhorage_paradox -.-> module_src_config_types_ts
104
+ module_src_index_ts["src/index.ts"]
105
+ module_src_model_buildModel_ts["src/model/buildModel.ts"]
106
+ package__ankhorage_paradox -.-> module_src_model_buildModel_ts
107
+ module_src_model_buildModel_ts --> module_src_model_types_ts
108
+ module_src_model_types_ts["src/model/types.ts"]
109
+ package__ankhorage_paradox -.-> module_src_model_types_ts
110
+ module_src_paths_policy_ts["src/paths/policy.ts"]
111
+ package__ankhorage_paradox -.-> module_src_paths_policy_ts
112
+ module_src_paths_policy_ts --> module_src_config_types_ts
113
+ module_src_render_render_ts["src/render/render.ts"]
114
+ package__ankhorage_paradox -.-> module_src_render_render_ts
115
+ module_src_render_render_ts --> module_src_model_types_ts
116
+ module_src_render_render_ts --> module_src_render_renderers_diagrams_ts
117
+ module_src_render_render_ts --> module_src_render_renderers_html_ts
118
+ module_src_render_render_ts --> module_src_render_renderers_markdown_ts
119
+ module_src_render_render_ts --> module_src_render_types_ts
120
+ module_src_render_renderers_diagrams_ts["src/render/renderers/diagrams.ts"]
121
+ package__ankhorage_paradox -.-> module_src_render_renderers_diagrams_ts
122
+ module_src_render_renderers_diagrams_ts --> module_src_model_types_ts
123
+ module_src_render_renderers_diagrams_ts --> module_src_render_types_ts
124
+ module_src_render_renderers_html_ts["src/render/renderers/html.ts"]
125
+ package__ankhorage_paradox -.-> module_src_render_renderers_html_ts
126
+ module_src_render_renderers_html_ts --> module_src_model_types_ts
127
+ module_src_render_renderers_html_ts --> module_src_render_types_ts
128
+ module_src_render_renderers_markdown_ts["src/render/renderers/markdown.ts"]
129
+ package__ankhorage_paradox -.-> module_src_render_renderers_markdown_ts
130
+ module_src_render_renderers_markdown_ts --> module_src_model_types_ts
131
+ module_src_render_renderers_markdown_ts --> module_src_render_types_ts
132
+ module_src_render_types_ts["src/render/types.ts"]
133
+ package__ankhorage_paradox -.-> module_src_render_types_ts
134
+ module_src_render_types_ts --> module_src_model_types_ts
135
+ module_src_write_write_ts["src/write/write.ts"]
136
+ package__ankhorage_paradox -.-> module_src_write_write_ts
137
+ module_src_write_write_ts --> module_src_config_types_ts
138
+ module_src_write_write_ts --> module_src_render_types_ts
139
+ ```
140
+
23
141
  ## Path resolution
24
142
 
25
143
  - Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).
@@ -35,6 +153,17 @@ export default defineParadoxConfig({
35
153
 
36
154
  Defines a Paradox configuration object without changing its shape.
37
155
 
156
+ - Kind: `function`
157
+ - Module: `src/config/defineParadoxConfig.ts`
158
+ - Source: `src/config/defineParadoxConfig.ts:6:1`
159
+ - Export paths: `src/index.ts`
160
+ - Related symbols: `ParadoxConfig`
161
+
38
162
  ### ParadoxConfig
39
163
 
40
164
  Configuration for running Paradox.
165
+
166
+ - Kind: `type`
167
+ - Module: `src/config/types.ts`
168
+ - Source: `src/config/types.ts:6:1`
169
+ - Export paths: `src/index.ts`
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { analyzeComponents } from './components.js';
4
4
  import { analyzeExports } from './exports.js';
5
+ import { analyzeModules } from './modules.js';
5
6
  import { createProject } from './project.js';
6
7
  import { createUsageFromPackageJson } from './usage.js';
7
8
  /***
@@ -18,12 +19,18 @@ export async function analyze(config, runtime) {
18
19
  entrypoints,
19
20
  });
20
21
  const components = analyzeComponents(exports);
22
+ const modules = analyzeModules(project, {
23
+ root,
24
+ entrypoints,
25
+ });
21
26
  return {
22
27
  packageName: config.docs?.title ?? pkg.name,
23
28
  packageId: pkg.name,
24
29
  description: config.docs?.description ?? pkg.description ?? null,
25
30
  exports,
26
31
  components,
32
+ entrypoints: entrypoints.map((entrypoint) => entrypoint.replaceAll('\\', '/')).sort(),
33
+ modules,
27
34
  usage,
28
35
  config: configMetadata,
29
36
  };
@@ -14,6 +14,9 @@ export function analyzeComponents(exports) {
14
14
  components.push({
15
15
  name: e.name,
16
16
  description: e.description,
17
+ modulePath: e.modulePath,
18
+ sourceLocation: e.sourceLocation,
19
+ exportPaths: e.exportPaths,
17
20
  props,
18
21
  });
19
22
  }
@@ -1,4 +1,5 @@
1
- import { isAbsolute, join, normalize } from 'node:path';
1
+ import { isAbsolute, join, normalize, relative } from 'node:path';
2
+ import { getExportMetadata } from './utils/getExportMetadata.js';
2
3
  import { getParadoxComment } from './utils/getParadoxComment.js';
3
4
  import { parseParadoxComment } from './utils/parseParadoxComment.js';
4
5
  import { resolveExportSymbol } from './utils/resolveExportSymbol.js';
@@ -6,9 +7,10 @@ import { resolveExportSymbol } from './utils/resolveExportSymbol.js';
6
7
  * Collects exported declarations from configured package entrypoints.
7
8
  */
8
9
  export function analyzeExports(project, options) {
9
- const exports = [];
10
+ const exportsByName = new Map();
10
11
  let config = null;
11
12
  for (const sourceFile of getEntryPointSourceFiles(project, options)) {
13
+ const entrypointPath = toPosixPath(relative(options.root, sourceFile.getFilePath()));
12
14
  const exported = sourceFile.getExportSymbols();
13
15
  for (const symbol of exported) {
14
16
  const resolved = resolveExportSymbol(symbol);
@@ -16,22 +18,44 @@ export function analyzeExports(project, options) {
16
18
  const rawComment = getParadoxComment(decl);
17
19
  const parsed = rawComment
18
20
  ? parseParadoxComment(rawComment)
19
- : { description: null, isConfig: false };
21
+ : { description: null, isConfig: false, params: {}, returns: null };
22
+ const name = resolved.getName();
20
23
  if (parsed.isConfig) {
21
24
  config = {
22
- exportName: resolved.getName(),
25
+ exportName: name,
23
26
  };
24
27
  }
25
- exports.push({
26
- name: resolved.getName(),
28
+ const metadata = getExportMetadata({
29
+ name,
27
30
  node: decl,
28
- description: parsed.description,
29
- kind: inferKind(decl),
31
+ root: options.root,
32
+ entrypointPath,
33
+ symbol: resolved,
30
34
  });
35
+ const existing = exportsByName.get(name);
36
+ exportsByName.set(name, existing
37
+ ? {
38
+ ...existing,
39
+ description: existing.description ?? parsed.description,
40
+ exportPaths: uniqueSorted([...existing.exportPaths, ...metadata.exportPaths]),
41
+ relatedSymbols: uniqueSorted([
42
+ ...existing.relatedSymbols,
43
+ ...metadata.relatedSymbols,
44
+ ]),
45
+ signatures: existing.signatures.length > 0 ? existing.signatures : metadata.signatures,
46
+ members: existing.members.length > 0 ? existing.members : metadata.members,
47
+ }
48
+ : {
49
+ name,
50
+ node: decl,
51
+ description: parsed.description,
52
+ kind: inferKind(decl),
53
+ ...metadata,
54
+ });
31
55
  }
32
56
  }
33
57
  return {
34
- exports,
58
+ exports: [...exportsByName.values()],
35
59
  config,
36
60
  };
37
61
  }
@@ -50,3 +74,9 @@ function inferKind(node) {
50
74
  return 'type';
51
75
  return 'unknown';
52
76
  }
77
+ function uniqueSorted(values) {
78
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
79
+ }
80
+ function toPosixPath(path) {
81
+ return path.replaceAll('\\', '/');
82
+ }
@@ -0,0 +1,9 @@
1
+ import type { Project } from 'ts-morph';
2
+ import type { AnalysisModule } from './types.js';
3
+ /***
4
+ * Builds a deterministic module relationship graph for documentation renderers.
5
+ */
6
+ export declare function analyzeModules(project: Project, options: {
7
+ root: string;
8
+ entrypoints: readonly string[];
9
+ }): AnalysisModule[];
@@ -0,0 +1,44 @@
1
+ import { isAbsolute, join, normalize, relative } from 'node:path';
2
+ /***
3
+ * Builds a deterministic module relationship graph for documentation renderers.
4
+ */
5
+ export function analyzeModules(project, options) {
6
+ const rootPath = normalize(options.root);
7
+ const entrypointPaths = new Set(options.entrypoints.map((entrypoint) => normalize(isAbsolute(entrypoint) ? entrypoint : join(options.root, entrypoint))));
8
+ return project
9
+ .getSourceFiles()
10
+ .filter((sourceFile) => {
11
+ const filePath = normalize(sourceFile.getFilePath());
12
+ const normalizedPath = toPosixPath(filePath);
13
+ return (!sourceFile.isDeclarationFile() &&
14
+ filePath.startsWith(rootPath) &&
15
+ !normalizedPath.includes('/node_modules/'));
16
+ })
17
+ .map((sourceFile) => {
18
+ const path = toPosixPath(relative(options.root, sourceFile.getFilePath()));
19
+ const dependencies = sourceFile
20
+ .getImportDeclarations()
21
+ .map((declaration) => declaration.getModuleSpecifierSourceFile())
22
+ .filter((dependency) => dependency != null)
23
+ .map((dependency) => normalize(dependency.getFilePath()))
24
+ .filter((dependency) => dependency.startsWith(rootPath) && !toPosixPath(dependency).includes('/node_modules/'))
25
+ .map((dependency) => toPosixPath(relative(options.root, dependency)));
26
+ const exports = sourceFile
27
+ .getExportSymbols()
28
+ .map((symbol) => symbol.getName())
29
+ .sort((left, right) => left.localeCompare(right));
30
+ return {
31
+ path,
32
+ isEntrypoint: entrypointPaths.has(normalize(sourceFile.getFilePath())),
33
+ dependencies: uniqueSorted(dependencies),
34
+ exports: uniqueSorted(exports),
35
+ };
36
+ })
37
+ .sort((left, right) => left.path.localeCompare(right.path));
38
+ }
39
+ function uniqueSorted(values) {
40
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
41
+ }
42
+ function toPosixPath(path) {
43
+ return path.replaceAll('\\', '/');
44
+ }
@@ -2,11 +2,41 @@ import type { Node } from 'ts-morph';
2
2
  /***
3
3
  * Describes one exported declaration discovered in a package.
4
4
  */
5
+ export interface AnalysisSourceLocation {
6
+ filePath: string;
7
+ line: number;
8
+ column: number;
9
+ }
10
+ export interface AnalysisParameter {
11
+ name: string;
12
+ type: string;
13
+ required: boolean;
14
+ description: string | null;
15
+ }
16
+ export interface AnalysisSignature {
17
+ label: string;
18
+ parameters: AnalysisParameter[];
19
+ returnType: string | null;
20
+ returnDescription: string | null;
21
+ }
22
+ export interface AnalysisMember {
23
+ name: string;
24
+ kind: 'property' | 'method';
25
+ type: string;
26
+ required: boolean;
27
+ description: string | null;
28
+ }
5
29
  export interface AnalysisExport {
6
30
  name: string;
7
31
  node: Node;
8
32
  description: string | null;
9
33
  kind: 'function' | 'type' | 'unknown';
34
+ modulePath: string;
35
+ sourceLocation: AnalysisSourceLocation;
36
+ exportPaths: string[];
37
+ relatedSymbols: string[];
38
+ signatures: AnalysisSignature[];
39
+ members: AnalysisMember[];
10
40
  }
11
41
  /***
12
42
  * Describes one React component and its extracted props.
@@ -14,6 +44,9 @@ export interface AnalysisExport {
14
44
  export interface AnalysisComponent {
15
45
  name: string;
16
46
  description: string | null;
47
+ modulePath: string;
48
+ sourceLocation: AnalysisSourceLocation;
49
+ exportPaths: string[];
17
50
  props: {
18
51
  name: string;
19
52
  type: string;
@@ -29,6 +62,12 @@ export interface AnalysisUsageCommand {
29
62
  name: string;
30
63
  command: string;
31
64
  }
65
+ export interface AnalysisModule {
66
+ path: string;
67
+ isEntrypoint: boolean;
68
+ dependencies: string[];
69
+ exports: string[];
70
+ }
32
71
  /***
33
72
  * Complete analysis output used to build the documentation model.
34
73
  */
@@ -38,6 +77,8 @@ export interface AnalysisResult {
38
77
  description: string | null;
39
78
  exports: AnalysisExport[];
40
79
  components: AnalysisComponent[];
80
+ entrypoints: string[];
81
+ modules: AnalysisModule[];
41
82
  usage: AnalysisUsage | null;
42
83
  config: {
43
84
  exportName: string;
@@ -0,0 +1,12 @@
1
+ import { Node, type Symbol as MorphSymbol } from 'ts-morph';
2
+ import type { AnalysisExport } from '../types.js';
3
+ /***
4
+ * Extracts computed metadata for an exported declaration.
5
+ */
6
+ export declare function getExportMetadata(options: {
7
+ name: string;
8
+ node: Node;
9
+ root: string;
10
+ entrypointPath: string;
11
+ symbol: MorphSymbol;
12
+ }): Pick<AnalysisExport, 'exportPaths' | 'members' | 'modulePath' | 'relatedSymbols' | 'signatures' | 'sourceLocation'>;
@@ -0,0 +1,162 @@
1
+ import { relative } from 'node:path';
2
+ import { Node, } from 'ts-morph';
3
+ import { getParadoxComment } from './getParadoxComment.js';
4
+ import { parseParadoxComment } from './parseParadoxComment.js';
5
+ /***
6
+ * Extracts computed metadata for an exported declaration.
7
+ */
8
+ export function getExportMetadata(options) {
9
+ const modulePath = toPosixPath(relative(options.root, options.node.getSourceFile().getFilePath()));
10
+ const sourceLocation = getSourceLocation(options.node, options.root);
11
+ const signatures = getSignatures(options.symbol, options.node);
12
+ const members = getMembers(options.node);
13
+ const relatedSymbols = collectRelatedSymbols(options.name, signatures.flatMap((signature) => [
14
+ ...signature.parameters.map((parameter) => parameter.type),
15
+ signature.returnType,
16
+ ]), members.map((member) => member.type));
17
+ return {
18
+ modulePath,
19
+ sourceLocation,
20
+ exportPaths: [options.entrypointPath],
21
+ relatedSymbols,
22
+ signatures,
23
+ members,
24
+ };
25
+ }
26
+ function getSourceLocation(node, root) {
27
+ const sourceFile = node.getSourceFile();
28
+ const { column, line } = sourceFile.getLineAndColumnAtPos(node.getStart(false));
29
+ return {
30
+ filePath: toPosixPath(relative(root, sourceFile.getFilePath())),
31
+ line,
32
+ column,
33
+ };
34
+ }
35
+ function getSignatures(symbol, node) {
36
+ const parsed = readParadoxMetadata(node);
37
+ const signatures = getCallableDeclarations(symbol, node).map((declaration) => getSignature(declaration, parsed.params, parsed.returns));
38
+ return uniqueBy(signatures.filter((signature) => signature.parameters.length > 0 ||
39
+ signature.returnType !== null ||
40
+ signature.returnDescription !== null), (signature) => signature.label);
41
+ }
42
+ function getSignature(declaration, params, returns) {
43
+ const normalizedParameters = declaration.getParameters().map((parameter) => {
44
+ const parameterDescription = params[parameter.getName()];
45
+ return {
46
+ name: parameter.getName(),
47
+ type: parameter.getType().getText(parameter),
48
+ required: !parameter.isOptional(),
49
+ description: parameterDescription ? parameterDescription.trim() : null,
50
+ };
51
+ });
52
+ const returnType = declaration.getReturnType().getText(declaration);
53
+ const parameterLabel = normalizedParameters
54
+ .map((parameter) => `${parameter.name}${parameter.required ? '' : '?'}: ${parameter.type}`)
55
+ .join(', ');
56
+ return {
57
+ label: `(${parameterLabel})${returnType === 'void' ? '' : ` => ${returnType}`}`,
58
+ parameters: normalizedParameters,
59
+ returnType,
60
+ returnDescription: returns,
61
+ };
62
+ }
63
+ function getMembers(node) {
64
+ if (getCallableNode(node) !== null)
65
+ return [];
66
+ return node
67
+ .getType()
68
+ .getProperties()
69
+ .map((property) => {
70
+ const [declaration] = property.getDeclarations();
71
+ const rawComment = getParadoxComment(declaration);
72
+ const parsed = rawComment
73
+ ? parseParadoxComment(rawComment)
74
+ : { description: null, isConfig: false, params: {}, returns: null };
75
+ return {
76
+ name: property.getName(),
77
+ kind: isMemberMethodDeclaration(declaration) ? 'method' : 'property',
78
+ type: property.getTypeAtLocation(declaration).getText(declaration),
79
+ required: !property.isOptional(),
80
+ description: parsed.description,
81
+ };
82
+ });
83
+ }
84
+ function getCallableDeclarations(symbol, node) {
85
+ const declarations = symbol
86
+ .getDeclarations()
87
+ .map((declaration) => getCallableNode(declaration))
88
+ .filter((declaration) => declaration !== null);
89
+ if (declarations.length > 0) {
90
+ return declarations;
91
+ }
92
+ const callableNode = getCallableNode(node);
93
+ return callableNode !== null ? [callableNode] : [];
94
+ }
95
+ function getCallableNode(node) {
96
+ if (Node.isFunctionDeclaration(node))
97
+ return node;
98
+ if (Node.isMethodDeclaration(node))
99
+ return node;
100
+ if (Node.isMethodSignature(node))
101
+ return node;
102
+ if (Node.isArrowFunction(node))
103
+ return node;
104
+ if (Node.isFunctionExpression(node))
105
+ return node;
106
+ if (Node.isVariableDeclaration(node)) {
107
+ const initializer = node.getInitializer();
108
+ if (initializer &&
109
+ (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer))) {
110
+ return initializer;
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+ function isMemberMethodDeclaration(node) {
116
+ return Node.isMethodDeclaration(node) || Node.isMethodSignature(node);
117
+ }
118
+ function readParadoxMetadata(node) {
119
+ const rawComment = getParadoxComment(node);
120
+ return rawComment
121
+ ? parseParadoxComment(rawComment)
122
+ : { description: null, isConfig: false, params: {}, returns: null };
123
+ }
124
+ function collectRelatedSymbols(exportName, ...values) {
125
+ const candidates = values.flatMap((entries) => entries).filter((entry) => entry !== null);
126
+ const related = new Set();
127
+ for (const value of candidates) {
128
+ for (const symbol of value.matchAll(/\b[A-Z][A-Za-z0-9_]*\b/g)) {
129
+ const [candidate] = symbol;
130
+ if (!IGNORED_RELATED_SYMBOLS.has(candidate) && candidate !== exportName) {
131
+ related.add(candidate);
132
+ }
133
+ }
134
+ }
135
+ return [...related].sort((left, right) => left.localeCompare(right));
136
+ }
137
+ function toPosixPath(path) {
138
+ return path.replaceAll('\\', '/');
139
+ }
140
+ function uniqueBy(items, key) {
141
+ const seen = new Set();
142
+ return items.filter((item) => {
143
+ const itemKey = key(item);
144
+ if (seen.has(itemKey))
145
+ return false;
146
+ seen.add(itemKey);
147
+ return true;
148
+ });
149
+ }
150
+ const IGNORED_RELATED_SYMBOLS = new Set([
151
+ 'Array',
152
+ 'Boolean',
153
+ 'Date',
154
+ 'Element',
155
+ 'JSX',
156
+ 'Map',
157
+ 'Promise',
158
+ 'ReadonlyArray',
159
+ 'Record',
160
+ 'Set',
161
+ 'String',
162
+ ]);
@@ -8,7 +8,9 @@ export function getPropsFromType(type) {
8
8
  const [declaration] = property.getDeclarations();
9
9
  const propertyType = property.getTypeAtLocation(declaration);
10
10
  const rawComment = getParadoxComment(declaration);
11
- const parsed = rawComment ? parseParadoxComment(rawComment) : { description: null };
11
+ const parsed = rawComment
12
+ ? parseParadoxComment(rawComment)
13
+ : { description: null, isConfig: false, params: {}, returns: null };
12
14
  return {
13
15
  name: property.getName(),
14
16
  type: propertyType.getText(declaration),
@@ -4,6 +4,8 @@
4
4
  interface ParsedParadoxComment {
5
5
  description: string | null;
6
6
  isConfig: boolean;
7
+ params: Record<string, string>;
8
+ returns: string | null;
7
9
  }
8
10
  /***
9
11
  * Parses a Paradox doc comment into structured metadata.
@@ -8,12 +8,28 @@ export function parseParadoxComment(rawComment) {
8
8
  .split('\n')
9
9
  .map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
10
10
  let isConfig = false;
11
+ const params = {};
12
+ let returns = null;
11
13
  const description = lines
12
14
  .filter((line) => {
13
- if (line.trimStart().startsWith('@config')) {
15
+ const trimmed = line.trimStart();
16
+ if (trimmed.startsWith('@config')) {
14
17
  isConfig = true;
15
18
  return false;
16
19
  }
20
+ if (trimmed.startsWith('@param ')) {
21
+ const paramBody = trimmed.slice('@param '.length).trim();
22
+ const [name, ...descriptionParts] = paramBody.split(/\s+/);
23
+ if (name) {
24
+ params[name] = descriptionParts.join(' ').trim();
25
+ }
26
+ return false;
27
+ }
28
+ if (trimmed.startsWith('@returns') || trimmed.startsWith('@return')) {
29
+ const returnBody = trimmed.replace(/^@returns?/, '').trim();
30
+ returns = returnBody.length > 0 ? returnBody : null;
31
+ return false;
32
+ }
17
33
  return true;
18
34
  })
19
35
  .join('\n')
@@ -21,5 +37,7 @@ export function parseParadoxComment(rawComment) {
21
37
  return {
22
38
  description: description.length > 0 ? description : null,
23
39
  isConfig,
40
+ params,
41
+ returns,
24
42
  };
25
43
  }
package/dist/cli.js CHANGED
@@ -14,10 +14,10 @@ async function main() {
14
14
  const configDir = dirname(configFilePath);
15
15
  const config = await loadParadoxConfig(configFilePath);
16
16
  const packageRoot = await resolvePackageRoot(config, configDir);
17
- const { outputRoot } = resolveOutputRoot(config, packageRoot);
17
+ const { outputDir, outputRoot } = resolveOutputRoot(config, packageRoot);
18
18
  const analysis = await analyze(config, { packageRoot });
19
19
  const model = buildModel(analysis);
20
- const result = render(model);
20
+ const result = render(model, { outputDir });
21
21
  await write(result, config, { packageRoot, outputRoot });
22
22
  }
23
23
  main().catch((error) => {