@reicek/neataptic-ts 0.1.25 → 0.1.26
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/.github/copilot-instructions.md +11 -0
- package/.github/skills/trace-analyzer-extension/SKILL.md +3 -3
- package/.github/skills/trace-analyzer-extension/assets/extension-checklist.md +1 -1
- package/.github/skills/trace-analyzer-extension/references/analyzer-extension-workflow.md +1 -1
- package/.github/skills/trace-audit-reporting/SKILL.md +3 -3
- package/.github/skills/trace-audit-reporting/references/trace-analysis-workflow.md +1 -1
- package/package.json +19 -13
- package/plans/Flappy_Bird_Folder_Documentation_Pass.md +4 -4
- package/plans/README.md +24 -0
- package/plans/Roadmap.md +62 -40
- package/plans/analyze-trace-solid-split.plans.md +66 -0
- package/plans/architecture-solid-split.plans.md +9 -15
- package/plans/asciiMaze-typescript-repair.plans.md +1 -1
- package/plans/generate-docs-solid-split.plans.md +87 -0
- package/plans/methods-docs.plans.md +25 -1
- package/plans/methods-solid-split.plans.md +14 -14
- package/plans/neat-docs.plans.md +9 -1
- package/plans/neat-test-surface-repair.plans.md +1 -1
- package/plans/render-docs-html-solid-split.plans.md +68 -0
- package/plans/src-no-explicit-any-cleanup.plans.md +1 -1
- package/plans/utils-docs.plans.md +6 -1
- package/scripts/analyze-trace/analyze-trace.analysis.ts +479 -0
- package/scripts/analyze-trace/analyze-trace.constants.ts +35 -0
- package/scripts/analyze-trace/analyze-trace.io.ts +69 -0
- package/scripts/analyze-trace/analyze-trace.report.ts +100 -0
- package/scripts/analyze-trace/analyze-trace.shared.ts +116 -0
- package/scripts/analyze-trace/analyze-trace.ts +45 -0
- package/scripts/analyze-trace/analyze-trace.types.ts +72 -0
- package/scripts/assets/theme.css +80 -23
- package/scripts/copy-examples.ts +239 -0
- package/scripts/export-onnx.ts +223 -0
- package/scripts/generate-bench-tables.ts +378 -37
- package/scripts/generate-docs/generate-docs.constants.ts +107 -0
- package/scripts/generate-docs/generate-docs.order.ts +355 -0
- package/scripts/generate-docs/generate-docs.state.ts +31 -0
- package/scripts/generate-docs/generate-docs.targets.ts +165 -0
- package/scripts/generate-docs/generate-docs.ts +63 -0
- package/scripts/generate-docs/generate-docs.types.ts +112 -0
- package/scripts/generate-docs/output/generate-docs.output.folder-index.utils.ts +167 -0
- package/scripts/generate-docs/output/generate-docs.output.ordering.utils.ts +353 -0
- package/scripts/generate-docs/output/generate-docs.output.readme.utils.ts +420 -0
- package/scripts/generate-docs/output/generate-docs.output.ts +123 -0
- package/scripts/generate-docs/output/generate-docs.output.warnings.utils.ts +219 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.collection.utils.ts +365 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.jsdoc.utils.ts +373 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.normalize.utils.ts +155 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.render.utils.ts +149 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.signature.utils.ts +289 -0
- package/scripts/generate-docs/symbols/generate-docs.symbols.ts +11 -0
- package/scripts/mermaid-cli.mjs +102 -22
- package/scripts/mermaid-cli.ts +736 -0
- package/scripts/render-docs-html/render-docs-html.assets.ts +54 -0
- package/scripts/render-docs-html/render-docs-html.mermaid.ts +245 -0
- package/scripts/{render-docs-html.sidebar.ts → render-docs-html/render-docs-html.navigation.ts} +141 -144
- package/scripts/render-docs-html/render-docs-html.pages.ts +333 -0
- package/scripts/render-docs-html/render-docs-html.shared.ts +333 -0
- package/scripts/render-docs-html/render-docs-html.types.ts +42 -0
- package/scripts/render-docs-html.ts +23 -587
- package/scripts/run-docs.ts +238 -0
- package/scripts/write-dist-docs-pkg.ts +40 -0
- package/src/README.md +75 -75
- package/src/architecture/connection/README.md +5 -5
- package/src/architecture/layer/README.md +508 -508
- package/src/architecture/network/README.md +1458 -1458
- package/src/architecture/network/activate/README.md +694 -694
- package/src/architecture/network/bootstrap/README.md +77 -77
- package/src/architecture/network/connect/README.md +74 -74
- package/src/architecture/network/deterministic/README.md +135 -135
- package/src/architecture/network/evolve/README.md +364 -364
- package/src/architecture/network/gating/README.md +130 -130
- package/src/architecture/network/genetic/README.md +399 -399
- package/src/architecture/network/mutate/README.md +897 -897
- package/src/architecture/network/onnx/README.md +720 -720
- package/src/architecture/network/onnx/export/README.md +728 -728
- package/src/architecture/network/onnx/export/layers/README.md +450 -450
- package/src/architecture/network/onnx/import/README.md +618 -618
- package/src/architecture/network/onnx/schema/README.md +32 -32
- package/src/architecture/network/prune/README.md +245 -245
- package/src/architecture/network/remove/README.md +135 -135
- package/src/architecture/network/runtime/README.md +106 -106
- package/src/architecture/network/serialize/README.md +542 -542
- package/src/architecture/network/slab/README.md +608 -608
- package/src/architecture/network/standalone/README.md +212 -212
- package/src/architecture/network/stats/README.md +84 -84
- package/src/architecture/network/topology/README.md +465 -465
- package/src/architecture/network/training/README.md +200 -200
- package/src/architecture/node/README.md +5 -5
- package/src/architecture/nodePool/README.md +14 -14
- package/src/methods/README.md +99 -99
- package/src/methods/activation/README.md +189 -189
- package/src/methods/cost/README.md +131 -131
- package/src/methods/rate/README.md +86 -86
- package/src/multithreading/README.md +77 -77
- package/src/multithreading/workers/browser/README.md +8 -8
- package/src/multithreading/workers/node/README.md +8 -8
- package/src/neat/README.md +148 -148
- package/src/neat/adaptive/README.md +120 -120
- package/src/neat/adaptive/acceptance/README.md +40 -40
- package/src/neat/adaptive/complexity/README.md +137 -137
- package/src/neat/adaptive/core/README.md +197 -197
- package/src/neat/adaptive/lineage/README.md +90 -90
- package/src/neat/adaptive/mutation/README.md +284 -284
- package/src/neat/compat/README.md +43 -43
- package/src/neat/compat/core/README.md +90 -90
- package/src/neat/diversity/README.md +35 -35
- package/src/neat/diversity/core/README.md +88 -88
- package/src/neat/evaluate/README.md +85 -85
- package/src/neat/evaluate/auto-distance/README.md +75 -75
- package/src/neat/evaluate/entropy-compat/README.md +37 -37
- package/src/neat/evaluate/entropy-sharing/README.md +43 -43
- package/src/neat/evaluate/fitness/README.md +23 -23
- package/src/neat/evaluate/novelty/README.md +120 -120
- package/src/neat/evaluate/objectives/README.md +17 -17
- package/src/neat/evaluate/shared/README.md +94 -94
- package/src/neat/evolve/README.md +96 -96
- package/src/neat/evolve/adaptive/README.md +60 -60
- package/src/neat/evolve/objectives/README.md +63 -63
- package/src/neat/evolve/offspring/README.md +56 -56
- package/src/neat/evolve/population/README.md +171 -171
- package/src/neat/evolve/runtime/README.md +79 -79
- package/src/neat/evolve/speciation/README.md +74 -74
- package/src/neat/evolve/warnings/README.md +10 -10
- package/src/neat/export/README.md +114 -114
- package/src/neat/helpers/README.md +50 -50
- package/src/neat/init/README.md +9 -9
- package/src/neat/lineage/core/README.md +101 -101
- package/src/neat/multiobjective/category/README.md +74 -74
- package/src/neat/multiobjective/crowding/README.md +272 -272
- package/src/neat/multiobjective/dominance/README.md +171 -171
- package/src/neat/multiobjective/fronts/README.md +68 -68
- package/src/neat/multiobjective/metrics/README.md +43 -43
- package/src/neat/multiobjective/objectives/README.md +31 -31
- package/src/neat/multiobjective/shared/README.md +27 -27
- package/src/neat/mutation/README.md +97 -97
- package/src/neat/mutation/add-conn/README.md +115 -115
- package/src/neat/mutation/add-node/README.md +126 -126
- package/src/neat/mutation/flow/README.md +149 -149
- package/src/neat/mutation/repair/README.md +185 -185
- package/src/neat/mutation/select/README.md +117 -117
- package/src/neat/mutation/shared/README.md +32 -32
- package/src/neat/objectives/README.md +25 -25
- package/src/neat/objectives/core/README.md +67 -67
- package/src/neat/pruning/README.md +40 -40
- package/src/neat/pruning/core/README.md +171 -171
- package/src/neat/pruning/facade/README.md +32 -32
- package/src/neat/rng/README.md +104 -104
- package/src/neat/rng/core/README.md +137 -137
- package/src/neat/rng/facade/README.md +50 -50
- package/src/neat/selection/README.md +111 -111
- package/src/neat/selection/core/README.md +227 -227
- package/src/neat/selection/facade/README.md +61 -61
- package/src/neat/shared/README.md +163 -163
- package/src/neat/speciation/README.md +31 -31
- package/src/neat/speciation/threshold/README.md +35 -35
- package/src/neat/species/README.md +25 -25
- package/src/neat/species/core/README.md +20 -20
- package/src/neat/species/core/shared/README.md +18 -18
- package/src/neat/species/history/context/README.md +22 -22
- package/src/neat/telemetry/accessors/README.md +58 -58
- package/src/neat/telemetry/exports/README.md +233 -233
- package/src/neat/telemetry/facade/README.md +252 -252
- package/src/neat/telemetry/facade/archive/README.md +57 -57
- package/src/neat/telemetry/facade/buffer/README.md +43 -43
- package/src/neat/telemetry/facade/lineage/README.md +12 -12
- package/src/neat/telemetry/facade/objectives/README.md +44 -44
- package/src/neat/telemetry/facade/runtime/README.md +26 -26
- package/src/neat/telemetry/facade/species/README.md +27 -27
- package/src/neat/telemetry/metrics/README.md +696 -696
- package/src/neat/telemetry/recorder/README.md +57 -57
- package/src/neat/telemetry/types/README.md +32 -32
- package/src/neat/topology-intent/README.md +75 -75
- package/src/utils/README.md +193 -193
- package/test/examples/asciiMaze/browser-entry/README.md +92 -92
- package/test/examples/asciiMaze/dashboardManager/README.md +109 -109
- package/test/examples/asciiMaze/dashboardManager/telemetry/README.md +28 -28
- package/test/examples/asciiMaze/evolutionEngine/README.md +1527 -1527
- package/test/examples/asciiMaze/mazeMovement/README.md +105 -105
- package/test/examples/asciiMaze/mazeMovement/finalization/README.md +16 -16
- package/test/examples/asciiMaze/mazeMovement/policy/README.md +57 -57
- package/test/examples/asciiMaze/mazeMovement/runtime/README.md +52 -52
- package/test/examples/asciiMaze/mazeMovement/shaping/README.md +46 -46
- package/test/examples/flappy_bird/browser-entry/README.md +508 -508
- package/test/examples/flappy_bird/browser-entry/host/README.md +101 -101
- package/test/examples/flappy_bird/browser-entry/host/resize/README.md +144 -144
- package/test/examples/flappy_bird/browser-entry/network-view/README.md +194 -194
- package/test/examples/flappy_bird/browser-entry/playback/README.md +278 -278
- package/test/examples/flappy_bird/browser-entry/playback/background/README.md +129 -129
- package/test/examples/flappy_bird/browser-entry/playback/background/ground-grid/README.md +502 -502
- package/test/examples/flappy_bird/browser-entry/playback/frame-render/README.md +139 -139
- package/test/examples/flappy_bird/browser-entry/playback/snapshot/README.md +10 -10
- package/test/examples/flappy_bird/browser-entry/playback/trail/README.md +43 -43
- package/test/examples/flappy_bird/browser-entry/playback/worker-channel/README.md +30 -30
- package/test/examples/flappy_bird/browser-entry/runtime/README.md +59 -59
- package/test/examples/flappy_bird/browser-entry/visualization/README.md +276 -276
- package/test/examples/flappy_bird/browser-entry/worker-channel/README.md +16 -16
- package/test/examples/flappy_bird/constants/README.md +1070 -1070
- package/test/examples/flappy_bird/environment/README.md +22 -22
- package/test/examples/flappy_bird/evaluation/README.md +32 -32
- package/test/examples/flappy_bird/evaluation/rollout/README.md +141 -141
- package/test/examples/flappy_bird/flappy-evolution-worker/README.md +425 -425
- package/test/examples/flappy_bird/simulation-shared/README.md +170 -170
- package/test/examples/flappy_bird/simulation-shared/observation/README.md +109 -109
- package/test/examples/flappy_bird/trainer/README.md +325 -325
- package/test/examples/flappy_bird/trainer/evaluation/README.md +74 -74
- package/scripts/analyze-trace.ts +0 -590
- package/scripts/copy-examples.mjs +0 -114
- package/scripts/export-onnx.mjs +0 -86
- package/scripts/generate-bench-tables.mjs +0 -182
- package/scripts/generate-docs.ts +0 -2900
- package/scripts/write-dist-docs-pkg.mjs +0 -16
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Parses file-summary and declaration-level JSDoc for the symbols boundary.
|
|
3
|
+
*
|
|
4
|
+
* The collector and renderer both depend on these helpers, so this chapter is
|
|
5
|
+
* the shared JSDoc vocabulary for symbol extraction, file-summary promotion,
|
|
6
|
+
* and tag normalization.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { JSDocTag, SourceFile } from 'ts-morph';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
RenderedFileSummary,
|
|
13
|
+
RenderedParameter,
|
|
14
|
+
} from '../generate-docs.types.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the renderable file-summary JSDoc for a source file.
|
|
18
|
+
*
|
|
19
|
+
* Precedence is intentionally stable so generated folder intros are
|
|
20
|
+
* predictable:
|
|
21
|
+
* 1. a leading top-of-file JSDoc block,
|
|
22
|
+
* 2. attached module-summary blocks that appear before the first declaration,
|
|
23
|
+
* 3. the first ordinary JSDoc description reachable from the file node.
|
|
24
|
+
*
|
|
25
|
+
* @param sourceFile - Source file to inspect.
|
|
26
|
+
* @returns File summary description/examples when present.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveFileSummary(
|
|
29
|
+
sourceFile: SourceFile,
|
|
30
|
+
): RenderedFileSummary {
|
|
31
|
+
const leadingFileSummary = extractLeadingFileJsDocSummary(sourceFile);
|
|
32
|
+
if (leadingFileSummary.description || leadingFileSummary.examples?.length) {
|
|
33
|
+
return leadingFileSummary;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const attachedFileSummary = extractAttachedFileJsDocSummary(sourceFile);
|
|
37
|
+
if (attachedFileSummary.description || attachedFileSummary.examples?.length) {
|
|
38
|
+
return attachedFileSummary;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
description: getFirstJsDocDescription(
|
|
43
|
+
sourceFile as unknown as { getJsDocs?: () => unknown[] },
|
|
44
|
+
),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns JSDoc blocks from a declaration-like node.
|
|
50
|
+
*
|
|
51
|
+
* @param node - Declaration-like node.
|
|
52
|
+
* @returns JSDoc array.
|
|
53
|
+
*/
|
|
54
|
+
export function getJsDocs(node: any): any[] {
|
|
55
|
+
return (node?.getJsDocs?.() as any[]) || [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolves the JSDoc blocks that should be used for a renderable declaration.
|
|
60
|
+
*
|
|
61
|
+
* Exported constants are commonly documented on their enclosing
|
|
62
|
+
* `VariableStatement` instead of on the inner `VariableDeclaration`. This
|
|
63
|
+
* helper falls back to that enclosing statement so generated README entries for
|
|
64
|
+
* constants keep their descriptions.
|
|
65
|
+
*
|
|
66
|
+
* @param node - Declaration-like node.
|
|
67
|
+
* @returns JSDoc array, preferring direct docs and falling back when needed.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveRenderableJsDocs(node: any): any[] {
|
|
70
|
+
const directJsDocs = getJsDocs(node);
|
|
71
|
+
if (directJsDocs.length > 0) {
|
|
72
|
+
return directJsDocs;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (node?.getKindName?.() !== 'VariableDeclaration') {
|
|
76
|
+
return directJsDocs;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const variableStatement =
|
|
80
|
+
node.getFirstAncestorByKindName?.('VariableStatement') ||
|
|
81
|
+
node.getVariableStatement?.();
|
|
82
|
+
return getJsDocs(variableStatement);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolves the first JSDoc description from a declaration-like node.
|
|
87
|
+
*
|
|
88
|
+
* @param node - Declaration-like node.
|
|
89
|
+
* @returns Description text when present.
|
|
90
|
+
*/
|
|
91
|
+
export function getFirstJsDocDescription(node: {
|
|
92
|
+
getJsDocs?: () => unknown[];
|
|
93
|
+
}): string | undefined {
|
|
94
|
+
const firstJsDoc = getJsDocs(node)[0];
|
|
95
|
+
return firstJsDoc?.getDescription?.()?.trim() as string | undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Checks whether any provided JSDoc block contains an internal tag.
|
|
100
|
+
*
|
|
101
|
+
* @param jsDocs - JSDoc array to inspect.
|
|
102
|
+
* @returns True when the declaration is marked internal.
|
|
103
|
+
*/
|
|
104
|
+
export function hasInternalTag(jsDocs: readonly any[]): boolean {
|
|
105
|
+
return jsDocs.some((jsDoc) =>
|
|
106
|
+
jsDoc
|
|
107
|
+
.getTags()
|
|
108
|
+
.some((tag: { getTagName(): string }) => tag.getTagName() === 'internal'),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Extracts rendered parameter docs from JSDoc tags.
|
|
114
|
+
*
|
|
115
|
+
* @param tags - JSDoc tags to inspect.
|
|
116
|
+
* @returns Parameter docs when present.
|
|
117
|
+
*/
|
|
118
|
+
export function extractParamDocs(
|
|
119
|
+
tags: readonly JSDocTag[],
|
|
120
|
+
): RenderedParameter[] | undefined {
|
|
121
|
+
const parameterDocs = tags
|
|
122
|
+
.filter((tag) => tag.getTagName() === 'param')
|
|
123
|
+
.map((tag) => {
|
|
124
|
+
const match = tag.getText().match(/@param\s+(\w+)/);
|
|
125
|
+
return {
|
|
126
|
+
name: match?.[1] || '',
|
|
127
|
+
doc: getTagCommentText(tag),
|
|
128
|
+
} satisfies RenderedParameter;
|
|
129
|
+
})
|
|
130
|
+
.filter((parameter) => Boolean(parameter.name));
|
|
131
|
+
|
|
132
|
+
return parameterDocs.length > 0 ? parameterDocs : undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Extracts rendered example blocks from JSDoc tags.
|
|
137
|
+
*
|
|
138
|
+
* @param tags - JSDoc tags to inspect.
|
|
139
|
+
* @returns Example blocks when present.
|
|
140
|
+
*/
|
|
141
|
+
export function extractExampleDocs(
|
|
142
|
+
tags: readonly JSDocTag[],
|
|
143
|
+
): string[] | undefined {
|
|
144
|
+
const examples = tags
|
|
145
|
+
.filter((tag) => tag.getTagName() === 'example')
|
|
146
|
+
.map((tag) => getExampleTagText(tag))
|
|
147
|
+
.filter((example): example is string => Boolean(example));
|
|
148
|
+
|
|
149
|
+
return examples.length > 0 ? examples : undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolves a tag comment into plain text.
|
|
154
|
+
*
|
|
155
|
+
* @param tag - JSDoc tag to inspect.
|
|
156
|
+
* @returns Flattened comment string when present.
|
|
157
|
+
*/
|
|
158
|
+
export function getTagCommentText(
|
|
159
|
+
tag: JSDocTag | undefined,
|
|
160
|
+
): string | undefined {
|
|
161
|
+
const rawComment = tag?.getComment();
|
|
162
|
+
if (typeof rawComment === 'string') {
|
|
163
|
+
return sanitizeTagCommentText(rawComment);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (Array.isArray(rawComment)) {
|
|
167
|
+
return sanitizeTagCommentText(
|
|
168
|
+
rawComment
|
|
169
|
+
.map(
|
|
170
|
+
(commentPart) =>
|
|
171
|
+
(commentPart as { getText?: () => string }).getText?.() ||
|
|
172
|
+
String(commentPart),
|
|
173
|
+
)
|
|
174
|
+
.join(' ')
|
|
175
|
+
.trim(),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Normalizes multiline tag blocks while preserving markdown structure.
|
|
184
|
+
*
|
|
185
|
+
* @param blockText - Multiline tag block text.
|
|
186
|
+
* @returns Cleaned block text or undefined when empty.
|
|
187
|
+
*/
|
|
188
|
+
export function sanitizeTagBlockText(
|
|
189
|
+
blockText: string | undefined,
|
|
190
|
+
): string | undefined {
|
|
191
|
+
const normalizedBlock = blockText
|
|
192
|
+
?.replace(/\r\n/g, '\n')
|
|
193
|
+
.replace(/^\s*\* ?/gm, '')
|
|
194
|
+
.replace(/^\n+/, '')
|
|
195
|
+
.replace(/\n+$/, '')
|
|
196
|
+
.trim();
|
|
197
|
+
|
|
198
|
+
return normalizedBlock ? normalizedBlock : undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Remove ts-morph JSDoc tag artifacts such as standalone trailing asterisks.
|
|
203
|
+
*
|
|
204
|
+
* @param commentText - Flattened tag comment text.
|
|
205
|
+
* @returns Cleaned comment text or undefined when nothing meaningful remains.
|
|
206
|
+
*/
|
|
207
|
+
function sanitizeTagCommentText(
|
|
208
|
+
commentText: string | undefined,
|
|
209
|
+
): string | undefined {
|
|
210
|
+
const normalizedComment = commentText
|
|
211
|
+
?.replace(/\r\n/g, '\n')
|
|
212
|
+
.replace(/\n\s*\*\s*$/g, '')
|
|
213
|
+
.replace(/^\s*\*\s*$/g, '')
|
|
214
|
+
.trim();
|
|
215
|
+
|
|
216
|
+
return normalizedComment ? normalizedComment : undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Extracts and parses the leading file-level JSDoc block from raw source text.
|
|
221
|
+
*
|
|
222
|
+
* @param sourceFile - Source file to inspect.
|
|
223
|
+
* @returns Parsed top-of-file summary description/examples when present.
|
|
224
|
+
*/
|
|
225
|
+
function extractLeadingFileJsDocSummary(
|
|
226
|
+
sourceFile: SourceFile,
|
|
227
|
+
): RenderedFileSummary {
|
|
228
|
+
const text = sourceFile.getFullText();
|
|
229
|
+
const match = text.match(/^\s*(?:\uFEFF)?\/\*\*([\s\S]*?)\*\//);
|
|
230
|
+
if (!match) {
|
|
231
|
+
return {};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const cleanedText = match[1]
|
|
235
|
+
.split(/\r?\n/)
|
|
236
|
+
.map((line) => line.replace(/^\s*\*\s?/, ''))
|
|
237
|
+
.join('\n')
|
|
238
|
+
.trim();
|
|
239
|
+
|
|
240
|
+
if (!cleanedText) {
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
cleanedText.split('\n').some((line) => line.trim().startsWith('@internal'))
|
|
246
|
+
) {
|
|
247
|
+
return {};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return parseFileSummaryBlock(cleanedText);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Parses a cleaned file-summary JSDoc block into description and examples.
|
|
255
|
+
*
|
|
256
|
+
* @param cleanedText - Cleaned raw file-summary block text.
|
|
257
|
+
* @returns Parsed description/examples.
|
|
258
|
+
*/
|
|
259
|
+
function parseFileSummaryBlock(cleanedText: string): RenderedFileSummary {
|
|
260
|
+
const tagStartIndex = cleanedText.search(/^@\w+/m);
|
|
261
|
+
const description = sanitizeTagBlockText(
|
|
262
|
+
tagStartIndex >= 0 ? cleanedText.slice(0, tagStartIndex) : cleanedText,
|
|
263
|
+
);
|
|
264
|
+
const examples = extractFileSummaryExamples(cleanedText);
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
description,
|
|
268
|
+
examples,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Extracts file-summary example blocks from a cleaned raw JSDoc block.
|
|
274
|
+
*
|
|
275
|
+
* @param cleanedText - Cleaned raw file-summary block text.
|
|
276
|
+
* @returns Example blocks when present.
|
|
277
|
+
*/
|
|
278
|
+
function extractFileSummaryExamples(cleanedText: string): string[] | undefined {
|
|
279
|
+
const exampleMatches = [
|
|
280
|
+
...cleanedText.matchAll(/(^|\n)@example\s*([\s\S]*?)(?=\n@\w+|$)/g),
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const examples = exampleMatches
|
|
284
|
+
.map((match) => sanitizeTagBlockText(match[2]))
|
|
285
|
+
.filter((example): example is string => Boolean(example));
|
|
286
|
+
|
|
287
|
+
return examples.length > 0 ? examples : undefined;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Extracts module-summary JSDoc blocks that are attached to the first
|
|
292
|
+
* top-level declaration after imports/exports.
|
|
293
|
+
*
|
|
294
|
+
* @param sourceFile - Source file to inspect.
|
|
295
|
+
* @returns Parsed description/examples when attached module-summary blocks exist.
|
|
296
|
+
*/
|
|
297
|
+
function extractAttachedFileJsDocSummary(
|
|
298
|
+
sourceFile: SourceFile,
|
|
299
|
+
): RenderedFileSummary {
|
|
300
|
+
const firstStatementWithMultipleJsDocs = sourceFile
|
|
301
|
+
.getStatements()
|
|
302
|
+
.find((statement) => {
|
|
303
|
+
const statementJsDocs = getJsDocs(statement);
|
|
304
|
+
return statementJsDocs.length > 1 && !hasInternalTag(statementJsDocs);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
if (!firstStatementWithMultipleJsDocs) {
|
|
308
|
+
return {};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const summaryJsDocs = getJsDocs(firstStatementWithMultipleJsDocs).slice(
|
|
312
|
+
0,
|
|
313
|
+
-1,
|
|
314
|
+
);
|
|
315
|
+
return mergeRenderedFileSummaries(summaryJsDocs.map(renderJsDocSummaryBlock));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Resolves an example tag into markdown-friendly multiline text.
|
|
320
|
+
*
|
|
321
|
+
* @param tag - JSDoc example tag.
|
|
322
|
+
* @returns Example block content when present.
|
|
323
|
+
*/
|
|
324
|
+
function getExampleTagText(tag: JSDocTag | undefined): string | undefined {
|
|
325
|
+
const rawTagText = tag?.getText()?.replace(/\r\n/g, '\n');
|
|
326
|
+
if (!rawTagText) {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const exampleBody = rawTagText.replace(/^@example\s*/, '');
|
|
331
|
+
return sanitizeTagBlockText(exampleBody);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Renders one JSDoc block into file-summary description/examples.
|
|
336
|
+
*
|
|
337
|
+
* @param jsDoc - JSDoc block to render.
|
|
338
|
+
* @returns Parsed description/examples.
|
|
339
|
+
*/
|
|
340
|
+
function renderJsDocSummaryBlock(jsDoc: any): RenderedFileSummary {
|
|
341
|
+
const description = sanitizeTagBlockText(jsDoc?.getDescription?.()?.trim());
|
|
342
|
+
const examples = extractExampleDocs(jsDoc?.getTags?.() || []);
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
description,
|
|
346
|
+
examples,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Merges multiple parsed file-summary blocks.
|
|
352
|
+
*
|
|
353
|
+
* @param summaries - Parsed summary blocks.
|
|
354
|
+
* @returns One merged file summary.
|
|
355
|
+
*/
|
|
356
|
+
function mergeRenderedFileSummaries(
|
|
357
|
+
summaries: readonly RenderedFileSummary[],
|
|
358
|
+
): RenderedFileSummary {
|
|
359
|
+
const descriptions = summaries
|
|
360
|
+
.map((summary) => summary.description)
|
|
361
|
+
.filter((description): description is string => Boolean(description));
|
|
362
|
+
const examples = summaries
|
|
363
|
+
.flatMap((summary) => summary.examples || [])
|
|
364
|
+
.filter(
|
|
365
|
+
(example, index, allExamples) => allExamples.indexOf(example) === index,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
description:
|
|
370
|
+
descriptions.length > 0 ? descriptions.join('\n\n') : undefined,
|
|
371
|
+
examples: examples.length > 0 ? examples : undefined,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Normalizes and deduplicates collected renderable symbols.
|
|
3
|
+
*
|
|
4
|
+
* The collector is intentionally permissive so it can gather multiple doc
|
|
5
|
+
* entry points. This chapter folds that raw set into stable names and merged
|
|
6
|
+
* JSDoc fields before README rendering begins.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
|
|
11
|
+
import { FILE_SUMMARY_SYMBOL_NAME } from '../generate-docs.constants.js';
|
|
12
|
+
import { extractCallSignatureParameterList } from './generate-docs.symbols.signature.utils.js';
|
|
13
|
+
import type {
|
|
14
|
+
DirectorySymbolMap,
|
|
15
|
+
RenderedSymbol,
|
|
16
|
+
} from '../generate-docs.types.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tidies and deduplicates collected symbols before README rendering.
|
|
20
|
+
*
|
|
21
|
+
* @param directorySymbolMap - Directory symbol map to normalize.
|
|
22
|
+
* @returns Nothing.
|
|
23
|
+
*/
|
|
24
|
+
export function dedupeDirectorySymbols(
|
|
25
|
+
directorySymbolMap: DirectorySymbolMap,
|
|
26
|
+
): void {
|
|
27
|
+
for (const [, fileSymbolMap] of directorySymbolMap) {
|
|
28
|
+
for (const [filePath, renderedSymbols] of fileSymbolMap) {
|
|
29
|
+
fileSymbolMap.set(filePath, dedupeFileSymbols(renderedSymbols, filePath));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Deduplicates symbols for a single file while conservatively merging JSDoc.
|
|
36
|
+
*
|
|
37
|
+
* @param renderedSymbols - Symbols collected for one file.
|
|
38
|
+
* @param filePath - Absolute source file path.
|
|
39
|
+
* @returns Deduplicated symbol list.
|
|
40
|
+
*/
|
|
41
|
+
function dedupeFileSymbols(
|
|
42
|
+
renderedSymbols: readonly RenderedSymbol[],
|
|
43
|
+
filePath: string,
|
|
44
|
+
): RenderedSymbol[] {
|
|
45
|
+
const seen = new Map<string, RenderedSymbol>();
|
|
46
|
+
const dedupedSymbols: RenderedSymbol[] = [];
|
|
47
|
+
|
|
48
|
+
for (const renderedSymbol of renderedSymbols) {
|
|
49
|
+
const normalizedName = normalizeName(renderedSymbol, filePath);
|
|
50
|
+
const dedupeKey = `${renderedSymbol.parent || ''}::${normalizedName}::${renderedSymbol.signature || ''}`;
|
|
51
|
+
const existingSymbol = seen.get(dedupeKey);
|
|
52
|
+
|
|
53
|
+
if (existingSymbol) {
|
|
54
|
+
mergeRenderedSymbols(existingSymbol, renderedSymbol);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const clonedSymbol: RenderedSymbol = {
|
|
59
|
+
...renderedSymbol,
|
|
60
|
+
name: normalizedName,
|
|
61
|
+
jsdoc: {
|
|
62
|
+
...renderedSymbol.jsdoc,
|
|
63
|
+
params: renderedSymbol.jsdoc.params?.slice(),
|
|
64
|
+
examples: renderedSymbol.jsdoc.examples?.slice(),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
seen.set(dedupeKey, clonedSymbol);
|
|
69
|
+
dedupedSymbols.push(clonedSymbol);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return dedupedSymbols;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Merges missing JSDoc fields from a duplicate symbol into the retained one.
|
|
77
|
+
*
|
|
78
|
+
* @param target - Existing retained symbol.
|
|
79
|
+
* @param incoming - Duplicate symbol carrying possible extra data.
|
|
80
|
+
* @returns Nothing.
|
|
81
|
+
*/
|
|
82
|
+
function mergeRenderedSymbols(
|
|
83
|
+
target: RenderedSymbol,
|
|
84
|
+
incoming: RenderedSymbol,
|
|
85
|
+
): void {
|
|
86
|
+
target.jsdoc.description =
|
|
87
|
+
target.jsdoc.description || incoming.jsdoc.description;
|
|
88
|
+
target.jsdoc.summary = target.jsdoc.summary || incoming.jsdoc.summary;
|
|
89
|
+
target.jsdoc.deprecated =
|
|
90
|
+
target.jsdoc.deprecated || incoming.jsdoc.deprecated;
|
|
91
|
+
|
|
92
|
+
if (incoming.jsdoc.examples?.length) {
|
|
93
|
+
target.jsdoc.examples = [
|
|
94
|
+
...(target.jsdoc.examples || []),
|
|
95
|
+
...incoming.jsdoc.examples.filter(
|
|
96
|
+
(incomingExample) =>
|
|
97
|
+
!(target.jsdoc.examples || []).includes(incomingExample),
|
|
98
|
+
),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (incoming.jsdoc.params?.length) {
|
|
103
|
+
target.jsdoc.params = (target.jsdoc.params || []).slice();
|
|
104
|
+
|
|
105
|
+
for (const parameter of incoming.jsdoc.params) {
|
|
106
|
+
const alreadyPresent = target.jsdoc.params.some(
|
|
107
|
+
(existingParameter) => existingParameter.name === parameter.name,
|
|
108
|
+
);
|
|
109
|
+
if (!alreadyPresent) {
|
|
110
|
+
target.jsdoc.params.push(parameter);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!target.signature && incoming.signature) {
|
|
116
|
+
target.signature = incoming.signature;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Normalizes rendered symbol names so generated headings remain stable.
|
|
122
|
+
*
|
|
123
|
+
* @param renderedSymbol - Symbol being normalized.
|
|
124
|
+
* @param filePath - Source file path used as fallback context.
|
|
125
|
+
* @returns Stable display name.
|
|
126
|
+
*/
|
|
127
|
+
function normalizeName(
|
|
128
|
+
renderedSymbol: RenderedSymbol,
|
|
129
|
+
filePath: string,
|
|
130
|
+
): string {
|
|
131
|
+
let name = String(renderedSymbol.name || '');
|
|
132
|
+
|
|
133
|
+
if (!name || name === 'default' || name === FILE_SUMMARY_SYMBOL_NAME) {
|
|
134
|
+
const fileBaseName = path.basename(filePath || '', '.ts');
|
|
135
|
+
|
|
136
|
+
if (renderedSymbol.kind === 'File' || name === FILE_SUMMARY_SYMBOL_NAME) {
|
|
137
|
+
return fileBaseName;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (renderedSymbol.parent) {
|
|
141
|
+
return `${renderedSymbol.parent}.${fileBaseName}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (renderedSymbol.signature) {
|
|
145
|
+
const parameterList = extractCallSignatureParameterList(
|
|
146
|
+
renderedSymbol.signature,
|
|
147
|
+
);
|
|
148
|
+
return `${fileBaseName}${parameterList ?? renderedSymbol.signature}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return fileBaseName;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return name.replace(/^function\s+/, '').replace(/\(\)$/, '');
|
|
155
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Renders declarations and exported symbols into the docs-ready symbol model.
|
|
3
|
+
*
|
|
4
|
+
* This chapter isolates ts-morph declaration quirks from the collector so the
|
|
5
|
+
* rest of the boundary can work with one normalized RenderedSymbol contract.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { JSDocTag, Symbol as MorphSymbol } from 'ts-morph';
|
|
9
|
+
|
|
10
|
+
import type { RenderedSymbol } from '../generate-docs.types.js';
|
|
11
|
+
import {
|
|
12
|
+
extractExampleDocs,
|
|
13
|
+
extractParamDocs,
|
|
14
|
+
getTagCommentText,
|
|
15
|
+
hasInternalTag,
|
|
16
|
+
resolveRenderableJsDocs,
|
|
17
|
+
} from './generate-docs.symbols.jsdoc.utils.js';
|
|
18
|
+
import { resolveCallSignature } from './generate-docs.symbols.signature.utils.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Renders one exported symbol when it belongs to a supported declaration kind
|
|
22
|
+
* and is not marked internal.
|
|
23
|
+
*
|
|
24
|
+
* @param symbol - Exported symbol.
|
|
25
|
+
* @param fallbackKind - Declaration kind used as a fallback label.
|
|
26
|
+
* @param filePath - Absolute source file path.
|
|
27
|
+
* @returns Rendered symbol or null when not supported.
|
|
28
|
+
*/
|
|
29
|
+
export function renderSymbol(
|
|
30
|
+
symbol: MorphSymbol,
|
|
31
|
+
fallbackKind: string,
|
|
32
|
+
filePath: string,
|
|
33
|
+
): RenderedSymbol | null {
|
|
34
|
+
const declaration = symbol.getDeclarations()[0];
|
|
35
|
+
if (!declaration) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const declarationKind = declaration.getKindName();
|
|
40
|
+
const supportedKinds =
|
|
41
|
+
/ClassDeclaration|FunctionDeclaration|InterfaceDeclaration|EnumDeclaration|TypeAliasDeclaration|VariableDeclaration/;
|
|
42
|
+
if (!supportedKinds.test(declarationKind)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const jsDocs = resolveRenderableJsDocs(declaration);
|
|
47
|
+
if (hasInternalTag(jsDocs)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const primaryJsDoc = jsDocs.at(-1);
|
|
52
|
+
const fullDescription = primaryJsDoc?.getDescription().trim();
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
kind: fallbackKind || declarationKind,
|
|
56
|
+
name: symbol.getName(),
|
|
57
|
+
parent: undefined,
|
|
58
|
+
filePath,
|
|
59
|
+
signature: resolveCallSignature(declaration),
|
|
60
|
+
jsdoc: {
|
|
61
|
+
summary: fullDescription?.split(/\r?\n\r?\n/)[0]?.trim(),
|
|
62
|
+
description: fullDescription,
|
|
63
|
+
params: extractParamDocs(primaryJsDoc?.getTags() || []),
|
|
64
|
+
examples: extractExampleDocs(primaryJsDoc?.getTags() || []),
|
|
65
|
+
returns: getTagCommentText(
|
|
66
|
+
primaryJsDoc
|
|
67
|
+
?.getTags()
|
|
68
|
+
.find(
|
|
69
|
+
(tag: JSDocTag) =>
|
|
70
|
+
tag.getTagName() === 'returns' || tag.getTagName() === 'return',
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
deprecated: getTagCommentText(
|
|
74
|
+
primaryJsDoc
|
|
75
|
+
?.getTags()
|
|
76
|
+
.find((tag: JSDocTag) => tag.getTagName() === 'deprecated'),
|
|
77
|
+
),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Renders a declaration-like node that may not have a directly usable symbol.
|
|
84
|
+
*
|
|
85
|
+
* @param declaration - Declaration-like node.
|
|
86
|
+
* @param forcedName - Optional forced display name.
|
|
87
|
+
* @param forcedKind - Optional forced kind label.
|
|
88
|
+
* @param filePath - Absolute source file path.
|
|
89
|
+
* @param parentName - Optional parent display name.
|
|
90
|
+
* @returns Rendered symbol or null when not renderable.
|
|
91
|
+
*/
|
|
92
|
+
export function renderDeclaration(
|
|
93
|
+
declaration: any,
|
|
94
|
+
forcedName?: string,
|
|
95
|
+
forcedKind?: string,
|
|
96
|
+
filePath?: string,
|
|
97
|
+
parentName?: string,
|
|
98
|
+
): RenderedSymbol | null {
|
|
99
|
+
if (!declaration) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const jsDocs = resolveRenderableJsDocs(declaration);
|
|
105
|
+
if (jsDocs.length === 0 || hasInternalTag(jsDocs)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const primaryJsDoc = jsDocs.at(-1);
|
|
110
|
+
const fullDescription = primaryJsDoc?.getDescription?.()?.trim();
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
kind:
|
|
114
|
+
forcedKind ||
|
|
115
|
+
declaration.getKindName?.() ||
|
|
116
|
+
declaration.getKind?.() ||
|
|
117
|
+
'Declaration',
|
|
118
|
+
name:
|
|
119
|
+
forcedName ||
|
|
120
|
+
declaration.getName?.() ||
|
|
121
|
+
declaration.getSymbol?.()?.getName?.() ||
|
|
122
|
+
(parentName ? `${parentName}.${forcedName}` : ''),
|
|
123
|
+
parent: parentName || undefined,
|
|
124
|
+
filePath: filePath || '',
|
|
125
|
+
signature: resolveCallSignature(declaration),
|
|
126
|
+
jsdoc: {
|
|
127
|
+
summary: fullDescription?.split(/\r?\n\r?\n/)[0]?.trim(),
|
|
128
|
+
description: fullDescription,
|
|
129
|
+
params: extractParamDocs(primaryJsDoc?.getTags() || []),
|
|
130
|
+
examples: extractExampleDocs(primaryJsDoc?.getTags() || []),
|
|
131
|
+
returns: getTagCommentText(
|
|
132
|
+
primaryJsDoc
|
|
133
|
+
?.getTags()
|
|
134
|
+
.find(
|
|
135
|
+
(tag: JSDocTag) =>
|
|
136
|
+
tag.getTagName() === 'returns' || tag.getTagName() === 'return',
|
|
137
|
+
),
|
|
138
|
+
),
|
|
139
|
+
deprecated: getTagCommentText(
|
|
140
|
+
primaryJsDoc
|
|
141
|
+
?.getTags()
|
|
142
|
+
.find((tag: JSDocTag) => tag.getTagName() === 'deprecated'),
|
|
143
|
+
),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|