@ankhorage/paradox 0.0.9 → 0.0.10

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 (38) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +63 -1
  3. package/dist/analyze/analyze.js +44 -2
  4. package/dist/analyze/components.d.ts +4 -1
  5. package/dist/analyze/components.js +13 -2
  6. package/dist/analyze/semantic/analyzeProject.d.ts +10 -0
  7. package/dist/analyze/semantic/analyzeProject.js +62 -0
  8. package/dist/analyze/semantic/associateDocBlocksWithSymbols.d.ts +5 -0
  9. package/dist/analyze/semantic/associateDocBlocksWithSymbols.js +51 -0
  10. package/dist/analyze/semantic/collectSourceFiles.d.ts +5 -0
  11. package/dist/analyze/semantic/collectSourceFiles.js +22 -0
  12. package/dist/analyze/semantic/createTypeScriptProgram.d.ts +13 -0
  13. package/dist/analyze/semantic/createTypeScriptProgram.js +23 -0
  14. package/dist/analyze/semantic/docBlocks.d.ts +10 -0
  15. package/dist/analyze/semantic/docBlocks.js +102 -0
  16. package/dist/analyze/semantic/exports.d.ts +29 -0
  17. package/dist/analyze/semantic/exports.js +399 -0
  18. package/dist/analyze/semantic/graphs.d.ts +13 -0
  19. package/dist/analyze/semantic/graphs.js +151 -0
  20. package/dist/analyze/semantic/isReactComponent.d.ts +5 -0
  21. package/dist/analyze/semantic/isReactComponent.js +30 -0
  22. package/dist/analyze/semantic/model.d.ts +109 -0
  23. package/dist/analyze/semantic/model.js +1 -0
  24. package/dist/analyze/semantic/paradoxComment.d.ts +16 -0
  25. package/dist/analyze/semantic/paradoxComment.js +64 -0
  26. package/dist/analyze/semantic/tagRegistry.d.ts +2 -0
  27. package/dist/analyze/semantic/tagRegistry.js +1 -0
  28. package/dist/analyze/semantic/utils.d.ts +4 -0
  29. package/dist/analyze/semantic/utils.js +13 -0
  30. package/dist/analyze/types.d.ts +44 -1
  31. package/dist/model/buildModel.d.ts +45 -7
  32. package/dist/model/buildModel.js +10 -0
  33. package/dist/model/types.d.ts +51 -8
  34. package/dist/paths/policy.d.ts +0 -15
  35. package/dist/paths/policy.js +0 -9
  36. package/dist/render/renderers/diagrams.js +11 -0
  37. package/dist/render/renderers/markdown.js +31 -7
  38. package/package.json +15 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 46bc15e: Add a reusable semantic TypeScript analysis foundation for exports, props, type members, and graph metadata.
8
+
3
9
  ## 0.0.9
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @ankhorage/paradox
2
2
 
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)
3
+ ![license: MIT](./paradox/badges/license.svg) ![npm: v0.0.9](./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
4
 
5
5
  Deterministic documentation generator for TypeScript packages.
6
6
 
@@ -22,6 +22,15 @@ export default defineParadoxConfig({
22
22
  });
23
23
  ```
24
24
 
25
+ ### Configuration options
26
+
27
+ | Field | Type | Required | Default | Description |
28
+ | ------- | --------------------------------------------------------- | -------- | ------- | ----------- |
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 | | |
33
+
25
34
  ## Generated documentation
26
35
 
27
36
  - [Interactive documentation app](./paradox/index.html)
@@ -46,6 +55,9 @@ graph TD
46
55
  module_src_analyze_analyze_ts --> module_src_analyze_exports_ts
47
56
  module_src_analyze_analyze_ts --> module_src_analyze_modules_ts
48
57
  module_src_analyze_analyze_ts --> module_src_analyze_project_ts
58
+ module_src_analyze_analyze_ts --> module_src_analyze_semantic_createTypeScriptProgram_ts
59
+ module_src_analyze_analyze_ts --> module_src_analyze_semantic_exports_ts
60
+ module_src_analyze_analyze_ts --> module_src_analyze_semantic_graphs_ts
49
61
  module_src_analyze_analyze_ts --> module_src_analyze_types_ts
50
62
  module_src_analyze_analyze_ts --> module_src_analyze_usage_ts
51
63
  module_src_analyze_analyze_ts --> module_src_config_types_ts
@@ -55,6 +67,8 @@ graph TD
55
67
  module_src_analyze_badges_ts --> module_src_analyze_usage_ts
56
68
  module_src_analyze_components_ts["src/analyze/components.ts"]
57
69
  package__ankhorage_paradox -.-> module_src_analyze_components_ts
70
+ module_src_analyze_components_ts --> module_src_analyze_semantic_exports_ts
71
+ module_src_analyze_components_ts --> module_src_analyze_semantic_model_ts
58
72
  module_src_analyze_components_ts --> module_src_analyze_types_ts
59
73
  module_src_analyze_components_ts --> module_src_analyze_utils_getComponentPropsType_ts
60
74
  module_src_analyze_components_ts --> module_src_analyze_utils_getPropsFromType_ts
@@ -71,6 +85,54 @@ graph TD
71
85
  module_src_analyze_modules_ts --> module_src_analyze_types_ts
72
86
  module_src_analyze_project_ts["src/analyze/project.ts"]
73
87
  package__ankhorage_paradox -.-> module_src_analyze_project_ts
88
+ module_src_analyze_semantic_analyzeProject_ts["src/analyze/semantic/analyzeProject.ts"]
89
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_analyzeProject_ts
90
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_associateDocBlocksWithSymbols_ts
91
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_collectSourceFiles_ts
92
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_createTypeScriptProgram_ts
93
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_docBlocks_ts
94
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_exports_ts
95
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_graphs_ts
96
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_model_ts
97
+ module_src_analyze_semantic_analyzeProject_ts --> module_src_analyze_semantic_tagRegistry_ts
98
+ module_src_analyze_semantic_associateDocBlocksWithSymbols_ts["src/analyze/semantic/associateDocBlocksWithSymbols.ts"]
99
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_associateDocBlocksWithSymbols_ts
100
+ module_src_analyze_semantic_associateDocBlocksWithSymbols_ts --> module_src_analyze_semantic_model_ts
101
+ module_src_analyze_semantic_associateDocBlocksWithSymbols_ts --> module_src_analyze_semantic_utils_ts
102
+ module_src_analyze_semantic_collectSourceFiles_ts["src/analyze/semantic/collectSourceFiles.ts"]
103
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_collectSourceFiles_ts
104
+ module_src_analyze_semantic_collectSourceFiles_ts --> module_src_analyze_semantic_model_ts
105
+ module_src_analyze_semantic_collectSourceFiles_ts --> module_src_analyze_semantic_utils_ts
106
+ module_src_analyze_semantic_createTypeScriptProgram_ts["src/analyze/semantic/createTypeScriptProgram.ts"]
107
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_createTypeScriptProgram_ts
108
+ module_src_analyze_semantic_createTypeScriptProgram_ts --> module_src_analyze_semantic_model_ts
109
+ module_src_analyze_semantic_createTypeScriptProgram_ts --> module_src_analyze_semantic_utils_ts
110
+ module_src_analyze_semantic_docBlocks_ts["src/analyze/semantic/docBlocks.ts"]
111
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_docBlocks_ts
112
+ module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_model_ts
113
+ module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_tagRegistry_ts
114
+ module_src_analyze_semantic_docBlocks_ts --> module_src_analyze_semantic_utils_ts
115
+ module_src_analyze_semantic_exports_ts["src/analyze/semantic/exports.ts"]
116
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_exports_ts
117
+ module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_isReactComponent_ts
118
+ module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_model_ts
119
+ module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_paradoxComment_ts
120
+ module_src_analyze_semantic_exports_ts --> module_src_analyze_semantic_utils_ts
121
+ module_src_analyze_semantic_graphs_ts["src/analyze/semantic/graphs.ts"]
122
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_graphs_ts
123
+ module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_exports_ts
124
+ module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_model_ts
125
+ module_src_analyze_semantic_graphs_ts --> module_src_analyze_semantic_utils_ts
126
+ module_src_analyze_semantic_isReactComponent_ts["src/analyze/semantic/isReactComponent.ts"]
127
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_isReactComponent_ts
128
+ module_src_analyze_semantic_model_ts["src/analyze/semantic/model.ts"]
129
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_model_ts
130
+ module_src_analyze_semantic_paradoxComment_ts["src/analyze/semantic/paradoxComment.ts"]
131
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_paradoxComment_ts
132
+ module_src_analyze_semantic_tagRegistry_ts["src/analyze/semantic/tagRegistry.ts"]
133
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_tagRegistry_ts
134
+ module_src_analyze_semantic_utils_ts["src/analyze/semantic/utils.ts"]
135
+ package__ankhorage_paradox -.-> module_src_analyze_semantic_utils_ts
74
136
  module_src_analyze_types_ts["src/analyze/types.ts"]
75
137
  package__ankhorage_paradox -.-> module_src_analyze_types_ts
76
138
  module_src_analyze_usage_ts["src/analyze/usage.ts"]
@@ -5,6 +5,9 @@ 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
12
  /***
10
13
  * Runs the source analysis pipeline for a configured package.
@@ -16,15 +19,37 @@ export async function analyze(config, runtime) {
16
19
  const badges = await analyzeBadges(root, pkg);
17
20
  const project = createProject(root);
18
21
  const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
22
+ const program = createTypeScriptProgram({ root, entrypoints, project });
19
23
  const { config: configMetadata, exports } = analyzeExports(project, {
20
24
  root,
21
25
  entrypoints,
22
26
  });
23
- const components = analyzeComponents(exports);
27
+ const components = analyzeComponents(exports, { program });
24
28
  const modules = analyzeModules(project, {
25
29
  root,
26
30
  entrypoints,
27
31
  });
32
+ const configExport = configMetadata
33
+ ? (exports.find((entry) => entry.name === configMetadata.exportName) ?? null)
34
+ : null;
35
+ const configMembers = configExport && (configExport.kind === 'type' || configExport.kind === 'unknown')
36
+ ? collectTypeMembers(program, resolveTypeReference(program, configExport.node) ?? {
37
+ type: configExport.node.getType(),
38
+ name: configExport.name,
39
+ sourcePath: configExport.modulePath,
40
+ symbol: configExport.node.getSymbol() ?? null,
41
+ })
42
+ : [];
43
+ const graphs = {
44
+ imports: collectImportGraph(program),
45
+ calls: collectCallGraph(program),
46
+ typeReferences: exports.flatMap((entry) => entry.relatedSymbols.map((symbol) => ({
47
+ fromSymbol: entry.name,
48
+ toType: symbol,
49
+ sourcePath: entry.modulePath,
50
+ }))),
51
+ componentComposition: collectComponentCompositionGraph(program),
52
+ };
28
53
  return {
29
54
  packageName: config.docs?.title ?? pkg.name,
30
55
  packageId: pkg.name,
@@ -35,9 +60,26 @@ export async function analyze(config, runtime) {
35
60
  modules,
36
61
  badges,
37
62
  usage,
38
- config: configMetadata,
63
+ config: configMetadata
64
+ ? {
65
+ exportName: configMetadata.exportName,
66
+ members: mapTypeMembers(configMembers),
67
+ }
68
+ : null,
69
+ graphs,
39
70
  };
40
71
  }
72
+ function mapTypeMembers(members) {
73
+ return members.map((member) => ({
74
+ name: member.name,
75
+ type: member.type,
76
+ required: member.required,
77
+ description: member.description ?? null,
78
+ ...(member.defaultValue !== undefined ? { defaultValue: member.defaultValue } : {}),
79
+ ...(member.inheritedFrom !== undefined ? { inheritedFrom: member.inheritedFrom } : {}),
80
+ ...(member.children ? { children: mapTypeMembers(member.children) } : {}),
81
+ }));
82
+ }
41
83
  async function readPackageJson(root) {
42
84
  const raw = await readFile(join(root, 'package.json'), 'utf-8');
43
85
  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,16 +1,27 @@
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
+ description: member.description ?? null,
21
+ })) ?? [];
12
22
  const propsType = getComponentPropsType(e.node);
13
- const props = propsType != null ? getPropsFromType(propsType) : [];
23
+ const legacyProps = propsType != null ? getPropsFromType(propsType) : [];
24
+ const props = analyzerProps.length > 0 ? analyzerProps : legacyProps;
14
25
  components.push({
15
26
  name: e.name,
16
27
  description: e.description,
@@ -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[];
@@ -0,0 +1,102 @@
1
+ import { defaultTagRegistry } from './tagRegistry.js';
2
+ import { relativeToRoot } from './utils.js';
3
+ const DOC_BLOCK_REGEX = /\/\*\*\*[\s\S]*?\*\//g;
4
+ /***
5
+ * Collects all Paradox doc blocks from a source file.
6
+ */
7
+ export function collectDocBlocks(sourceFile, options) {
8
+ const { program } = options;
9
+ const tagRegistry = options.tagRegistry ?? defaultTagRegistry;
10
+ const text = sourceFile.getFullText();
11
+ const sourcePath = relativeToRoot(program.root, sourceFile.getFilePath());
12
+ const blocks = [];
13
+ for (const match of text.matchAll(DOC_BLOCK_REGEX)) {
14
+ const [raw = ''] = match;
15
+ const start = match.index;
16
+ const end = start + raw.length;
17
+ const { line, column } = sourceFile.getLineAndColumnAtPos(start);
18
+ const parsed = parseDocBlock(raw, tagRegistry);
19
+ const id = `${sourcePath}:${line}:${column}`;
20
+ blocks.push({
21
+ id,
22
+ sourcePath,
23
+ start,
24
+ end,
25
+ line,
26
+ column,
27
+ raw,
28
+ description: parsed.description,
29
+ params: parsed.params,
30
+ returns: parsed.returns,
31
+ tags: parsed.tags,
32
+ });
33
+ }
34
+ return blocks;
35
+ }
36
+ /***
37
+ * Extracts registered tags from a doc block.
38
+ */
39
+ function collectTags(raw, tagRegistry = defaultTagRegistry) {
40
+ const tags = [];
41
+ const lines = normalizeDocBlock(raw);
42
+ for (const line of lines) {
43
+ const trimmed = line.trim();
44
+ if (!trimmed.startsWith('@'))
45
+ continue;
46
+ const [tagName, ...rest] = trimmed.slice(1).split(/\s+/);
47
+ if (!tagName || !tagRegistry.has(tagName))
48
+ continue;
49
+ const value = rest.join(' ').trim();
50
+ tags.push({
51
+ name: tagName,
52
+ value: value.length > 0 ? value : null,
53
+ });
54
+ }
55
+ return tags;
56
+ }
57
+ function parseDocBlock(raw, tagRegistry) {
58
+ const lines = normalizeDocBlock(raw);
59
+ const tags = collectTags(raw, tagRegistry);
60
+ const params = {};
61
+ let returns = null;
62
+ const description = lines
63
+ .filter((line) => {
64
+ const trimmed = line.trim();
65
+ if (!trimmed.startsWith('@'))
66
+ return true;
67
+ const [tagName, ...rest] = trimmed.slice(1).split(/\s+/);
68
+ if (!tagName)
69
+ return false;
70
+ if (tagName === 'param') {
71
+ const [name, ...descParts] = rest;
72
+ if (name) {
73
+ params[name] = descParts.join(' ').trim();
74
+ }
75
+ return false;
76
+ }
77
+ if (tagName === 'returns' || tagName === 'return') {
78
+ const returnBody = rest.join(' ').trim();
79
+ returns = returnBody.length > 0 ? returnBody : null;
80
+ return false;
81
+ }
82
+ if (tagRegistry.has(tagName)) {
83
+ return false;
84
+ }
85
+ return true;
86
+ })
87
+ .join('\n')
88
+ .trim();
89
+ return {
90
+ description: description.length > 0 ? description : null,
91
+ tags,
92
+ params,
93
+ returns,
94
+ };
95
+ }
96
+ function normalizeDocBlock(raw) {
97
+ return raw
98
+ .replace(/^\/\*\*\*/, '')
99
+ .replace(/\*\/$/, '')
100
+ .split('\n')
101
+ .map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
102
+ }
@@ -0,0 +1,29 @@
1
+ import type { Node, Symbol as MorphSymbol, Type } from 'ts-morph';
2
+ import type { AnalyzedExport, AnalyzedProgram, AnalyzedProps, AnalyzedTypeMember, ResolvedTypeReference } from './model.js';
3
+ /***
4
+ * Collects exported declarations from configured entrypoints.
5
+ */
6
+ export declare function collectExports(program: AnalyzedProgram): AnalyzedExport[];
7
+ /***
8
+ * Returns a human-readable signature for callable exports.
9
+ */
10
+ export declare function getExportSignature(_program: AnalyzedProgram, analyzedExport: Pick<AnalyzedExport, 'node'>): string | undefined;
11
+ /***
12
+ * Resolves a named type reference from a node or symbol.
13
+ */
14
+ export declare function resolveTypeReference(program: AnalyzedProgram, typeNodeOrSymbol: Node | Type | MorphSymbol): ResolvedTypeReference | null;
15
+ /***
16
+ * Collects nested type members for interfaces and type literals.
17
+ */
18
+ export declare function collectTypeMembers(program: AnalyzedProgram, reference: ResolvedTypeReference, options?: {
19
+ inheritedFrom?: string;
20
+ depth?: number;
21
+ }): AnalyzedTypeMember[];
22
+ /***
23
+ * Extracts props metadata for a callable export.
24
+ */
25
+ export declare function collectPropsForExport(program: AnalyzedProgram, analyzedExport: Pick<AnalyzedExport, 'name' | 'node'>): AnalyzedProps | undefined;
26
+ /***
27
+ * Collects related types referenced by an export.
28
+ */
29
+ export declare function collectRelatedTypes(analyzedExport: Pick<AnalyzedExport, 'name' | 'signature' | 'props' | 'typeMembers'>): string[];