@ankhorage/paradox 0.0.9 → 0.1.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +117 -13
  3. package/dist/analyze/analyze.d.ts +0 -3
  4. package/dist/analyze/analyze.js +47 -13
  5. package/dist/analyze/components.d.ts +4 -1
  6. package/dist/analyze/components.js +16 -2
  7. package/dist/analyze/exports.d.ts +1 -0
  8. package/dist/analyze/exports.js +16 -3
  9. package/dist/analyze/semantic/analyzeProject.d.ts +10 -0
  10. package/dist/analyze/semantic/analyzeProject.js +62 -0
  11. package/dist/analyze/semantic/associateDocBlocksWithSymbols.d.ts +5 -0
  12. package/dist/analyze/semantic/associateDocBlocksWithSymbols.js +51 -0
  13. package/dist/analyze/semantic/collectSourceFiles.d.ts +5 -0
  14. package/dist/analyze/semantic/collectSourceFiles.js +22 -0
  15. package/dist/analyze/semantic/createTypeScriptProgram.d.ts +13 -0
  16. package/dist/analyze/semantic/createTypeScriptProgram.js +23 -0
  17. package/dist/analyze/semantic/docBlocks.d.ts +10 -0
  18. package/dist/analyze/semantic/docBlocks.js +102 -0
  19. package/dist/analyze/semantic/exports.d.ts +29 -0
  20. package/dist/analyze/semantic/exports.js +399 -0
  21. package/dist/analyze/semantic/graphs.d.ts +13 -0
  22. package/dist/analyze/semantic/graphs.js +151 -0
  23. package/dist/analyze/semantic/isReactComponent.d.ts +5 -0
  24. package/dist/analyze/semantic/isReactComponent.js +30 -0
  25. package/dist/analyze/semantic/model.d.ts +109 -0
  26. package/dist/analyze/semantic/model.js +1 -0
  27. package/dist/analyze/semantic/paradoxComment.d.ts +16 -0
  28. package/dist/analyze/semantic/paradoxComment.js +64 -0
  29. package/dist/analyze/semantic/tagRegistry.d.ts +2 -0
  30. package/dist/analyze/semantic/tagRegistry.js +1 -0
  31. package/dist/analyze/semantic/utils.d.ts +4 -0
  32. package/dist/analyze/semantic/utils.js +13 -0
  33. package/dist/analyze/types.d.ts +55 -1
  34. package/dist/analyze/utils/parseParadoxComment.d.ts +7 -0
  35. package/dist/analyze/utils/parseParadoxComment.js +61 -14
  36. package/dist/config/defineParadoxConfig.d.ts +2 -0
  37. package/dist/config/defineParadoxConfig.js +2 -0
  38. package/dist/config/types.d.ts +1 -0
  39. package/dist/model/buildModel.d.ts +56 -7
  40. package/dist/model/buildModel.js +16 -0
  41. package/dist/model/types.d.ts +62 -8
  42. package/dist/paths/policy.d.ts +0 -15
  43. package/dist/paths/policy.js +0 -9
  44. package/dist/render/renderers/diagrams.js +11 -0
  45. package/dist/render/renderers/markdown.js +235 -39
  46. package/package.json +15 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dd41897: Generate compact README docs from `@readme` symbols.
8
+
9
+ ## 0.0.10
10
+
11
+ ### Patch Changes
12
+
13
+ - 46bc15e: Add a reusable semantic TypeScript analysis foundation for exports, props, type members, and graph metadata.
14
+
3
15
  ## 0.0.9
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -1,15 +1,41 @@
1
+ <!-- markdownlint-disable MD013 MD033 -->
2
+ <!-- This file is generated by Paradox. Do not edit manually. -->
3
+
1
4
  # @ankhorage/paradox
2
5
 
3
- ![license: MIT](./paradox/badges/license.svg) ![npm: v0.0.8](./paradox/badges/npm.svg) ![runtime: bun](./paradox/badges/runtime.svg) ![typescript: strict](./paradox/badges/typescript.svg) ![eslint: checked](./paradox/badges/eslint.svg) ![prettier: checked](./paradox/badges/prettier.svg) ![build: checked](./paradox/badges/build.svg) ![tests: checked](./paradox/badges/tests.svg) ![docs: paradox](./paradox/badges/docs.svg)
6
+ ![license: MIT](./paradox/badges/license.svg) ![npm: v0.0.10](./paradox/badges/npm.svg) ![runtime: bun](./paradox/badges/runtime.svg) ![typescript: strict](./paradox/badges/typescript.svg) ![eslint: checked](./paradox/badges/eslint.svg) ![prettier: checked](./paradox/badges/prettier.svg) ![build: checked](./paradox/badges/build.svg) ![tests: checked](./paradox/badges/tests.svg) ![docs: paradox](./paradox/badges/docs.svg)
4
7
 
5
8
  Deterministic documentation generator for TypeScript packages.
6
9
 
7
- ## Usage
10
+ ## Installation
8
11
 
9
12
  ```bash
10
13
  bunx @ankhorage/paradox
11
14
  ```
12
15
 
16
+ ## Documentation Tags
17
+
18
+ <details>
19
+ <summary>@readme</summary>
20
+
21
+ Includes a documentation block or exported symbol in README output.
22
+
23
+ </details>
24
+
25
+ <details>
26
+ <summary>@config</summary>
27
+
28
+ 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.
29
+
30
+ </details>
31
+
32
+ <details>
33
+ <summary>@example</summary>
34
+
35
+ Adds a titled fenced code example to the generated documentation for a symbol.
36
+
37
+ </details>
38
+
13
39
  ## Configuration
14
40
 
15
41
  Create a `paradox.config.ts` file:
@@ -22,6 +48,18 @@ export default defineParadoxConfig({
22
48
  });
23
49
  ```
24
50
 
51
+ <details>
52
+ <summary>Configuration options</summary>
53
+
54
+ | Field | Type | Required | Default | Description |
55
+ | ------- | --------------------------------------------------------- | -------- | ------- | ----------- |
56
+ | mode | `'safe' \| 'write' \| undefined` | no | — | |
57
+ | docs | `{ title?: string; description?: string; } \| undefined` | no | — | |
58
+ | package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no | — | |
59
+ | output | `{ dir?: string; } \| undefined` | no | — | |
60
+
61
+ </details>
62
+
25
63
  ## Generated documentation
26
64
 
27
65
  - [Interactive documentation app](./paradox/index.html)
@@ -34,6 +72,9 @@ export default defineParadoxConfig({
34
72
 
35
73
  ## Architecture preview
36
74
 
75
+ <details>
76
+ <summary>Architecture overview</summary>
77
+
37
78
  ```mermaid
38
79
  graph TD
39
80
  package__ankhorage_paradox["@ankhorage/paradox"]
@@ -46,6 +87,9 @@ graph TD
46
87
  module_src_analyze_analyze_ts --> module_src_analyze_exports_ts
47
88
  module_src_analyze_analyze_ts --> module_src_analyze_modules_ts
48
89
  module_src_analyze_analyze_ts --> module_src_analyze_project_ts
90
+ module_src_analyze_analyze_ts --> module_src_analyze_semantic_createTypeScriptProgram_ts
91
+ module_src_analyze_analyze_ts --> module_src_analyze_semantic_exports_ts
92
+ module_src_analyze_analyze_ts --> module_src_analyze_semantic_graphs_ts
49
93
  module_src_analyze_analyze_ts --> module_src_analyze_types_ts
50
94
  module_src_analyze_analyze_ts --> module_src_analyze_usage_ts
51
95
  module_src_analyze_analyze_ts --> module_src_config_types_ts
@@ -55,6 +99,8 @@ graph TD
55
99
  module_src_analyze_badges_ts --> module_src_analyze_usage_ts
56
100
  module_src_analyze_components_ts["src/analyze/components.ts"]
57
101
  package__ankhorage_paradox -.-> module_src_analyze_components_ts
102
+ module_src_analyze_components_ts --> module_src_analyze_semantic_exports_ts
103
+ module_src_analyze_components_ts --> module_src_analyze_semantic_model_ts
58
104
  module_src_analyze_components_ts --> module_src_analyze_types_ts
59
105
  module_src_analyze_components_ts --> module_src_analyze_utils_getComponentPropsType_ts
60
106
  module_src_analyze_components_ts --> module_src_analyze_utils_getPropsFromType_ts
@@ -71,6 +117,54 @@ graph TD
71
117
  module_src_analyze_modules_ts --> module_src_analyze_types_ts
72
118
  module_src_analyze_project_ts["src/analyze/project.ts"]
73
119
  package__ankhorage_paradox -.-> module_src_analyze_project_ts
120
+ module_src_analyze_semantic_analyzeProject_ts["src/analyze/semantic/analyzeProject.ts"]
121
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_analyzeProject_ts
122
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_associateDocBlocksWithSymbols_ts
123
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_collectSourceFiles_ts
124
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_createTypeScriptProgram_ts
125
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_docBlocks_ts
126
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_exports_ts
127
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_graphs_ts
128
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_model_ts
129
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_tagRegistry_ts
130
+ module_src_analyze_semantic_associateDocBlocksWithSymbols_ts["src/analyze/semantic/associateDocBlocksWithSymbols.ts"]
131
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_associateDocBlocksWithSymbols_ts
132
+ module_src_analyze_semantic_associateDocBlocksWithSymbols_ts --> module_src_analyze_semantic_model_ts
133
+ module_src_analyze_semantic_associateDocBlocksWithSymbols_ts --> module_src_analyze_semantic_utils_ts
134
+ module_src_analyze_semantic_collectSourceFiles_ts["src/analyze/semantic/collectSourceFiles.ts"]
135
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_collectSourceFiles_ts
136
+ module_src_analyze_semantic_collectSourceFiles_ts --> module_src_analyze_semantic_model_ts
137
+ module_src_analyze_semantic_collectSourceFiles_ts --> module_src_analyze_semantic_utils_ts
138
+ module_src_analyze_semantic_createTypeScriptProgram_ts["src/analyze/semantic/createTypeScriptProgram.ts"]
139
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_createTypeScriptProgram_ts
140
+ module_src_analyze_semantic_createTypeScriptProgram_ts --> module_src_analyze_semantic_model_ts
141
+ module_src_analyze_semantic_createTypeScriptProgram_ts --> module_src_analyze_semantic_utils_ts
142
+ module_src_analyze_semantic_docBlocks_ts["src/analyze/semantic/docBlocks.ts"]
143
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_docBlocks_ts
144
+ module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_model_ts
145
+ module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_tagRegistry_ts
146
+ module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_utils_ts
147
+ module_src_analyze_semantic_exports_ts["src/analyze/semantic/exports.ts"]
148
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_exports_ts
149
+ module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_isReactComponent_ts
150
+ module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_model_ts
151
+ module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_paradoxComment_ts
152
+ module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_utils_ts
153
+ module_src_analyze_semantic_graphs_ts["src/analyze/semantic/graphs.ts"]
154
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_graphs_ts
155
+ module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_exports_ts
156
+ module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_model_ts
157
+ module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_utils_ts
158
+ module_src_analyze_semantic_isReactComponent_ts["src/analyze/semantic/isReactComponent.ts"]
159
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_isReactComponent_ts
160
+ module_src_analyze_semantic_model_ts["src/analyze/semantic/model.ts"]
161
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_model_ts
162
+ module_src_analyze_semantic_paradoxComment_ts["src/analyze/semantic/paradoxComment.ts"]
163
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_paradoxComment_ts
164
+ module_src_analyze_semantic_tagRegistry_ts["src/analyze/semantic/tagRegistry.ts"]
165
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_tagRegistry_ts
166
+ module_src_analyze_semantic_utils_ts["src/analyze/semantic/utils.ts"]
167
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_utils_ts
74
168
  module_src_analyze_types_ts["src/analyze/types.ts"]
75
169
  package__ankhorage_paradox -.-> module_src_analyze_types_ts
76
170
  module_src_analyze_usage_ts["src/analyze/usage.ts"]
@@ -150,6 +244,8 @@ graph TD
150
244
  module_src_write_write_ts --> module_src_render_types_ts
151
245
  ```
152
246
 
247
+ </details>
248
+
153
249
  ## Path resolution
154
250
 
155
251
  - Config discovery: searches upward from `process.cwd()` for `paradox.config.ts/js/mjs/cjs` (required; no fallback).
@@ -161,21 +257,29 @@ graph TD
161
257
 
162
258
  ## Public API
163
259
 
164
- ### defineParadoxConfig
260
+ ### Configuration
261
+
262
+ <details>
263
+ <summary>defineParadoxConfig</summary>
264
+
265
+ ```ts
266
+ defineParadoxConfig(config: ParadoxConfig) => ParadoxConfig
267
+ ```
165
268
 
166
269
  Defines a Paradox configuration object without changing its shape.
167
270
 
168
- - Kind: `function`
169
- - Module: `src/config/defineParadoxConfig.ts`
170
- - Source: `src/config/defineParadoxConfig.ts:6:1`
171
- - Export paths: `src/index.ts`
172
- - Related symbols: `ParadoxConfig`
271
+ Module: `src/config/defineParadoxConfig.ts`
272
+ Source: `src/config/defineParadoxConfig.ts:8:1`
273
+ Related symbols: `ParadoxConfig`
173
274
 
174
- ### ParadoxConfig
275
+ </details>
276
+
277
+ <details>
278
+ <summary>ParadoxConfig</summary>
175
279
 
176
280
  Configuration for running Paradox.
177
281
 
178
- - Kind: `type`
179
- - Module: `src/config/types.ts`
180
- - Source: `src/config/types.ts:6:1`
181
- - Export paths: `src/index.ts`
282
+ Module: `src/config/types.ts`
283
+ Source: `src/config/types.ts:7:1`
284
+
285
+ </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>;
@@ -5,10 +5,10 @@ import { analyzeComponents } from './components.js';
5
5
  import { analyzeExports } from './exports.js';
6
6
  import { analyzeModules } from './modules.js';
7
7
  import { createProject } from './project.js';
8
+ import { createTypeScriptProgram } from './semantic/createTypeScriptProgram.js';
9
+ import { collectTypeMembers, resolveTypeReference } from './semantic/exports.js';
10
+ import { collectCallGraph, collectComponentCompositionGraph, collectImportGraph, } from './semantic/graphs.js';
8
11
  import { createUsageFromPackageJson } from './usage.js';
9
- /***
10
- * Runs the source analysis pipeline for a configured package.
11
- */
12
12
  export async function analyze(config, runtime) {
13
13
  const root = runtime.packageRoot;
14
14
  const pkg = await readPackageJson(root);
@@ -16,15 +16,31 @@ export async function analyze(config, runtime) {
16
16
  const badges = await analyzeBadges(root, pkg);
17
17
  const project = createProject(root);
18
18
  const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
19
- const { config: configMetadata, exports } = analyzeExports(project, {
20
- root,
21
- entrypoints,
22
- });
23
- const components = analyzeComponents(exports);
24
- const modules = analyzeModules(project, {
25
- root,
26
- entrypoints,
27
- });
19
+ const program = createTypeScriptProgram({ root, entrypoints, project });
20
+ const { config: configMetadata, exports } = analyzeExports(project, { root, entrypoints });
21
+ const components = analyzeComponents(exports, { program });
22
+ const modules = analyzeModules(project, { root, entrypoints });
23
+ const configExport = configMetadata
24
+ ? (exports.find((entry) => entry.name === configMetadata.exportName) ?? null)
25
+ : null;
26
+ const configMembers = configExport && (configExport.kind === 'type' || configExport.kind === 'unknown')
27
+ ? collectTypeMembers(program, resolveTypeReference(program, configExport.node) ?? {
28
+ type: configExport.node.getType(),
29
+ name: configExport.name,
30
+ sourcePath: configExport.modulePath,
31
+ symbol: configExport.node.getSymbol() ?? null,
32
+ })
33
+ : [];
34
+ const graphs = {
35
+ imports: collectImportGraph(program),
36
+ calls: collectCallGraph(program),
37
+ typeReferences: exports.flatMap((entry) => entry.relatedSymbols.map((symbol) => ({
38
+ fromSymbol: entry.name,
39
+ toType: symbol,
40
+ sourcePath: entry.modulePath,
41
+ }))),
42
+ componentComposition: collectComponentCompositionGraph(program),
43
+ };
28
44
  return {
29
45
  packageName: config.docs?.title ?? pkg.name,
30
46
  packageId: pkg.name,
@@ -35,9 +51,27 @@ export async function analyze(config, runtime) {
35
51
  modules,
36
52
  badges,
37
53
  usage,
38
- config: configMetadata,
54
+ config: configMetadata
55
+ ? {
56
+ exportName: configMetadata.exportName,
57
+ isReadme: configMetadata.isReadme,
58
+ members: mapTypeMembers(configMembers),
59
+ }
60
+ : null,
61
+ graphs,
39
62
  };
40
63
  }
64
+ function mapTypeMembers(members) {
65
+ return members.map((member) => ({
66
+ name: member.name,
67
+ type: member.type,
68
+ required: member.required,
69
+ description: member.description ?? null,
70
+ ...(member.defaultValue !== undefined ? { defaultValue: member.defaultValue } : {}),
71
+ ...(member.inheritedFrom !== undefined ? { inheritedFrom: member.inheritedFrom } : {}),
72
+ ...(member.children ? { children: mapTypeMembers(member.children) } : {}),
73
+ }));
74
+ }
41
75
  async function readPackageJson(root) {
42
76
  const raw = await readFile(join(root, 'package.json'), 'utf-8');
43
77
  return JSON.parse(raw);
@@ -1,5 +1,8 @@
1
+ import type { AnalyzedProgram } from './semantic/model.js';
1
2
  import type { AnalysisComponent, AnalysisExport } from './types.js';
2
3
  /***
3
4
  * Extracts React components and their props from analyzed exports.
4
5
  */
5
- export declare function analyzeComponents(exports: readonly AnalysisExport[]): AnalysisComponent[];
6
+ export declare function analyzeComponents(exports: readonly AnalysisExport[], options?: {
7
+ program?: AnalyzedProgram;
8
+ }): AnalysisComponent[];
@@ -1,19 +1,33 @@
1
+ import { collectPropsForExport } from './semantic/exports.js';
1
2
  import { getComponentPropsType } from './utils/getComponentPropsType.js';
2
3
  import { getPropsFromType } from './utils/getPropsFromType.js';
3
4
  import { isReactComponent } from './utils/isReactComponent.js';
4
5
  /***
5
6
  * Extracts React components and their props from analyzed exports.
6
7
  */
7
- export function analyzeComponents(exports) {
8
+ export function analyzeComponents(exports, options = {}) {
8
9
  const components = [];
9
10
  for (const e of exports) {
10
11
  if (!isReactComponent(e.node))
11
12
  continue;
13
+ const propsFromAnalyzer = options.program
14
+ ? collectPropsForExport(options.program, { name: e.name, node: e.node })
15
+ : undefined;
16
+ const analyzerProps = propsFromAnalyzer?.members.map((member) => ({
17
+ name: member.name,
18
+ type: member.type,
19
+ required: member.required,
20
+ ...(member.defaultValue !== undefined ? { defaultValue: member.defaultValue } : {}),
21
+ description: member.description ?? null,
22
+ })) ?? [];
12
23
  const propsType = getComponentPropsType(e.node);
13
- const props = propsType != null ? getPropsFromType(propsType) : [];
24
+ const legacyProps = propsType != null ? getPropsFromType(propsType) : [];
25
+ const props = analyzerProps.length > 0 ? analyzerProps : legacyProps;
14
26
  components.push({
15
27
  name: e.name,
16
28
  description: e.description,
29
+ isReadme: e.isReadme,
30
+ examples: e.examples,
17
31
  modulePath: e.modulePath,
18
32
  sourceLocation: e.sourceLocation,
19
33
  exportPaths: e.exportPaths,
@@ -4,6 +4,7 @@ interface AnalyzeExportsResult {
4
4
  exports: AnalysisExport[];
5
5
  config: {
6
6
  exportName: string;
7
+ isReadme: boolean;
7
8
  } | null;
8
9
  }
9
10
  /***
@@ -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,10 @@
1
+ import type { AnalyzedProject } from './model.js';
2
+ interface AnalyzeProjectConfig {
3
+ root: string;
4
+ entrypoints?: string[];
5
+ }
6
+ /***
7
+ * Runs the semantic analyzer pipeline for a project.
8
+ */
9
+ export declare function analyzeProject(config: AnalyzeProjectConfig): AnalyzedProject;
10
+ export {};
@@ -0,0 +1,62 @@
1
+ import { associateDocBlocksWithSymbols } from './associateDocBlocksWithSymbols.js';
2
+ import { collectSourceFiles } from './collectSourceFiles.js';
3
+ import { createTypeScriptProgram } from './createTypeScriptProgram.js';
4
+ import { collectDocBlocks } from './docBlocks.js';
5
+ import { collectExports, collectPropsForExport, collectRelatedTypes, collectTypeMembers, getExportSignature, resolveTypeReference, } from './exports.js';
6
+ import { collectCallGraph, collectComponentCompositionGraph, collectImportGraph, } from './graphs.js';
7
+ import { defaultTagRegistry } from './tagRegistry.js';
8
+ /***
9
+ * Runs the semantic analyzer pipeline for a project.
10
+ */
11
+ export function analyzeProject(config) {
12
+ const program = createTypeScriptProgram({
13
+ root: config.root,
14
+ entrypoints: config.entrypoints,
15
+ });
16
+ const files = collectSourceFiles(program);
17
+ const docBlocks = files.flatMap((file) => collectDocBlocks(file.sourceFile, { program, tagRegistry: defaultTagRegistry }));
18
+ const exports = associateDocBlocksWithSymbols(program, docBlocks, collectExports(program)).map((entry) => {
19
+ const signature = getExportSignature(program, entry);
20
+ const props = collectPropsForExport(program, entry);
21
+ const typeReference = resolveTypeReference(program, entry.node);
22
+ const typeMembers = typeReference && (entry.kind === 'interface' || entry.kind === 'type')
23
+ ? collectTypeMembers(program, typeReference)
24
+ : undefined;
25
+ const relatedTypes = collectRelatedTypes({
26
+ name: entry.name,
27
+ signature,
28
+ props,
29
+ typeMembers,
30
+ });
31
+ return {
32
+ ...entry,
33
+ signature,
34
+ props,
35
+ typeMembers,
36
+ relatedTypes,
37
+ };
38
+ });
39
+ const tags = new Map();
40
+ for (const docBlock of docBlocks) {
41
+ for (const tag of docBlock.tags) {
42
+ tags.set(tag.name, tag.value ?? null);
43
+ }
44
+ }
45
+ const graphs = {
46
+ imports: collectImportGraph(program),
47
+ calls: collectCallGraph(program),
48
+ typeReferences: exports.flatMap((entry) => entry.relatedTypes.map((typeName) => ({
49
+ fromSymbol: entry.name,
50
+ toType: typeName,
51
+ sourcePath: entry.sourcePath,
52
+ }))),
53
+ componentComposition: collectComponentCompositionGraph(program, exports),
54
+ };
55
+ return {
56
+ files,
57
+ exports,
58
+ docBlocks,
59
+ tags: [...tags.entries()].map(([name, value]) => ({ name, value })),
60
+ graphs,
61
+ };
62
+ }
@@ -0,0 +1,5 @@
1
+ import type { AnalyzedDocBlock, AnalyzedExport, AnalyzedProgram } from './model.js';
2
+ /***
3
+ * Attaches doc blocks and tags to their nearest symbol declarations.
4
+ */
5
+ export declare function associateDocBlocksWithSymbols(program: AnalyzedProgram, docBlocks: readonly AnalyzedDocBlock[], exports: readonly AnalyzedExport[]): AnalyzedExport[];
@@ -0,0 +1,51 @@
1
+ import { relativeToRoot } from './utils.js';
2
+ /***
3
+ * Attaches doc blocks and tags to their nearest symbol declarations.
4
+ */
5
+ export function associateDocBlocksWithSymbols(program, docBlocks, exports) {
6
+ const docBlocksByFile = new Map();
7
+ for (const docBlock of docBlocks) {
8
+ const entries = docBlocksByFile.get(docBlock.sourcePath);
9
+ if (entries) {
10
+ entries.push(docBlock);
11
+ }
12
+ else {
13
+ docBlocksByFile.set(docBlock.sourcePath, [docBlock]);
14
+ }
15
+ }
16
+ for (const [, entries] of docBlocksByFile) {
17
+ entries.sort((left, right) => left.start - right.start);
18
+ }
19
+ return exports.map((entry) => {
20
+ const sourceFile = entry.node.getSourceFile();
21
+ const sourcePath = relativeToRoot(program.root, sourceFile.getFilePath());
22
+ const docBlocksForFile = docBlocksByFile.get(sourcePath) ?? [];
23
+ const nodeStart = entry.node.getStart(false);
24
+ let match = undefined;
25
+ for (const block of docBlocksForFile) {
26
+ if (block.end > nodeStart)
27
+ break;
28
+ if (block.end > 0) {
29
+ match = block;
30
+ }
31
+ }
32
+ if (match) {
33
+ const text = sourceFile.getFullText();
34
+ const between = text.slice(match.end, nodeStart);
35
+ if (!/^[\s;]*(export\s+)?(default\s+)?$/.test(between)) {
36
+ match = undefined;
37
+ }
38
+ }
39
+ if (!match) {
40
+ return {
41
+ ...entry,
42
+ tags: entry.tags,
43
+ };
44
+ }
45
+ return {
46
+ ...entry,
47
+ docBlock: match,
48
+ tags: match.tags,
49
+ };
50
+ });
51
+ }
@@ -0,0 +1,5 @@
1
+ import type { AnalyzedFile, AnalyzedProgram } from './model.js';
2
+ /***
3
+ * Collects stable source file metadata for the analyzer.
4
+ */
5
+ export declare function collectSourceFiles(program: AnalyzedProgram): AnalyzedFile[];
@@ -0,0 +1,22 @@
1
+ import { normalize } from 'node:path';
2
+ import { isNodeModulePath, isPathInsideRoot, relativeToRoot } from './utils.js';
3
+ /***
4
+ * Collects stable source file metadata for the analyzer.
5
+ */
6
+ export function collectSourceFiles(program) {
7
+ const entrypoints = new Set(program.entrypointFilePaths.map((entrypoint) => normalize(entrypoint)));
8
+ return program.project
9
+ .getSourceFiles()
10
+ .filter((sourceFile) => {
11
+ const filePath = sourceFile.getFilePath();
12
+ return (!sourceFile.isDeclarationFile() &&
13
+ isPathInsideRoot(program.root, filePath) &&
14
+ !isNodeModulePath(filePath));
15
+ })
16
+ .map((sourceFile) => ({
17
+ path: relativeToRoot(program.root, sourceFile.getFilePath()),
18
+ sourceFile,
19
+ isEntrypoint: entrypoints.has(normalize(sourceFile.getFilePath())),
20
+ }))
21
+ .sort((left, right) => left.path.localeCompare(right.path));
22
+ }
@@ -0,0 +1,13 @@
1
+ import { Project } from 'ts-morph';
2
+ import type { AnalyzedProgram } from './model.js';
3
+ interface CreateProgramOptions {
4
+ root: string;
5
+ entrypoints?: string[];
6
+ project?: Project;
7
+ tsconfigPath?: string;
8
+ }
9
+ /***
10
+ * Creates or reuses a ts-morph project for analysis.
11
+ */
12
+ export declare function createTypeScriptProgram(options: CreateProgramOptions): AnalyzedProgram;
13
+ export {};
@@ -0,0 +1,23 @@
1
+ import { isAbsolute, join, normalize } from 'node:path';
2
+ import { Project } from 'ts-morph';
3
+ import { toPosixPath } from './utils.js';
4
+ /***
5
+ * Creates or reuses a ts-morph project for analysis.
6
+ */
7
+ export function createTypeScriptProgram(options) {
8
+ const root = normalize(options.root);
9
+ const entrypoints = options.entrypoints ?? ['src/index.ts'];
10
+ const entrypointFilePaths = entrypoints.map((entrypoint) => normalize(isAbsolute(entrypoint) ? entrypoint : join(root, entrypoint)));
11
+ const project = options.project ??
12
+ new Project({
13
+ tsConfigFilePath: options.tsconfigPath ?? join(root, 'tsconfig.json'),
14
+ skipAddingFilesFromTsConfig: false,
15
+ });
16
+ return {
17
+ project,
18
+ typeChecker: project.getTypeChecker(),
19
+ root,
20
+ entrypoints: entrypoints.map((entrypoint) => toPosixPath(entrypoint)),
21
+ entrypointFilePaths,
22
+ };
23
+ }
@@ -0,0 +1,10 @@
1
+ import type { SourceFile } from 'ts-morph';
2
+ import type { AnalyzedDocBlock, AnalyzedProgram } from './model.js';
3
+ import type { TagRegistry } from './tagRegistry.js';
4
+ /***
5
+ * Collects all Paradox doc blocks from a source file.
6
+ */
7
+ export declare function collectDocBlocks(sourceFile: SourceFile, options: {
8
+ program: AnalyzedProgram;
9
+ tagRegistry?: TagRegistry;
10
+ }): AnalyzedDocBlock[];