@ankhorage/paradox 0.1.27 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +53 -61
- package/dist/analyze/analyze.d.ts +2 -2
- package/dist/analyze/analyze.js +51 -35
- package/dist/analyze/badges.d.ts +2 -1
- package/dist/analyze/badges.js +14 -4
- package/dist/analyze/components.js +12 -11
- package/dist/analyze/documentation/collectDocumentationCommentsAsync.d.ts +11 -0
- package/dist/analyze/documentation/collectDocumentationCommentsAsync.js +72 -0
- package/dist/analyze/documentation/findings.d.ts +5 -0
- package/dist/analyze/documentation/findings.js +17 -0
- package/dist/analyze/documentation/validateDocumentationPolicyAsync.d.ts +13 -0
- package/dist/analyze/documentation/validateDocumentationPolicyAsync.js +150 -0
- package/dist/analyze/documentation/validateReferencesAsync.d.ts +11 -0
- package/dist/analyze/documentation/validateReferencesAsync.js +148 -0
- package/dist/analyze/exports.d.ts +4 -0
- package/dist/analyze/exports.js +14 -7
- package/dist/analyze/readmeConfig.d.ts +1 -3
- package/dist/analyze/readmeConfig.js +8 -19
- package/dist/analyze/readmeUsage.d.ts +7 -10
- package/dist/analyze/readmeUsage.js +124 -48
- package/dist/analyze/semantic/docBlocks.js +18 -48
- package/dist/analyze/semantic/exports.js +1 -3
- package/dist/analyze/semantic/model.d.ts +0 -2
- package/dist/analyze/semantic/paradoxComment.d.ts +1 -11
- package/dist/analyze/semantic/paradoxComment.js +1 -43
- package/dist/analyze/semantic/tagRegistry.js +2 -1
- package/dist/analyze/sourceFunctions.js +4 -4
- package/dist/analyze/types.d.ts +31 -24
- package/dist/analyze/usage.d.ts +2 -2
- package/dist/analyze/usage.js +3 -33
- package/dist/analyze/utils/getExportMetadata.js +11 -40
- package/dist/analyze/utils/parseParadoxComment.d.ts +12 -9
- package/dist/analyze/utils/parseParadoxComment.js +66 -78
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.js +3 -2
- package/dist/cli/standalone.js +11 -0
- package/dist/config/defineParadoxConfig.d.ts +1 -1
- package/dist/doc-tags/registry.d.ts +28 -32
- package/dist/doc-tags/registry.js +35 -39
- package/dist/index.d.ts +1 -1
- package/dist/model/buildModel.d.ts +26 -19
- package/dist/model/buildModel.js +33 -99
- package/dist/model/types.d.ts +27 -20
- package/dist/paths/policy.d.ts +1 -1
- package/dist/render/renderers/diagrams.js +1 -7
- package/dist/render/renderers/html.js +89 -58
- package/dist/render/renderers/markdown.js +139 -86
- package/dist/render/toFileStem.d.ts +2 -0
- package/dist/render/toFileStem.js +8 -0
- package/dist/{config/types.d.ts → types/config.d.ts} +3 -5
- package/dist/write/write.d.ts +1 -1
- package/package.json +2 -1
- package/dist/analyze/readmeCli.d.ts +0 -9
- package/dist/analyze/readmeCli.js +0 -33
- package/dist/analyze/utils/getLeadingParadoxComment.d.ts +0 -10
- package/dist/analyze/utils/getLeadingParadoxComment.js +0 -16
- /package/dist/{config/types.js → types/config.js} +0 -0
|
@@ -16,9 +16,8 @@ export function collectDocBlocks(sourceFile, options) {
|
|
|
16
16
|
const end = start + raw.length;
|
|
17
17
|
const { line, column } = sourceFile.getLineAndColumnAtPos(start);
|
|
18
18
|
const parsed = parseDocBlock(raw, tagRegistry);
|
|
19
|
-
const id = `${sourcePath}:${line}:${column}`;
|
|
20
19
|
blocks.push({
|
|
21
|
-
id
|
|
20
|
+
id: `${sourcePath}:${line}:${column}`,
|
|
22
21
|
sourcePath,
|
|
23
22
|
start,
|
|
24
23
|
end,
|
|
@@ -26,8 +25,6 @@ export function collectDocBlocks(sourceFile, options) {
|
|
|
26
25
|
column,
|
|
27
26
|
raw,
|
|
28
27
|
description: parsed.description,
|
|
29
|
-
params: parsed.params,
|
|
30
|
-
returns: parsed.returns,
|
|
31
28
|
tags: parsed.tags,
|
|
32
29
|
});
|
|
33
30
|
}
|
|
@@ -37,62 +34,35 @@ export function collectDocBlocks(sourceFile, options) {
|
|
|
37
34
|
* Extracts registered tags from a doc block.
|
|
38
35
|
*/
|
|
39
36
|
function collectTags(raw, tagRegistry = defaultTagRegistry) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
tags.push({
|
|
51
|
-
name: tagName,
|
|
52
|
-
value: value.length > 0 ? value : null,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return tags;
|
|
37
|
+
return normalizeDocBlock(raw).flatMap((line) => {
|
|
38
|
+
const match = /^@([A-Za-z][A-Za-z0-9-]*)(?:\s+(.*))?$/.exec(line.trim());
|
|
39
|
+
if (match === null)
|
|
40
|
+
return [];
|
|
41
|
+
const [, name] = match;
|
|
42
|
+
if (!tagRegistry.has(name))
|
|
43
|
+
return [];
|
|
44
|
+
const value = match.slice(2).join('').trim();
|
|
45
|
+
return [{ name, value: value.length > 0 ? value : null }];
|
|
46
|
+
});
|
|
56
47
|
}
|
|
48
|
+
/***
|
|
49
|
+
* Parses semantic description and supported tags without interpreting unsupported tag syntax.
|
|
50
|
+
*/
|
|
57
51
|
function parseDocBlock(raw, tagRegistry) {
|
|
58
52
|
const lines = normalizeDocBlock(raw);
|
|
59
53
|
const tags = collectTags(raw, tagRegistry);
|
|
60
|
-
const params = {};
|
|
61
|
-
let returns = null;
|
|
62
54
|
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
|
-
})
|
|
55
|
+
.filter((line) => !/^@[A-Za-z][A-Za-z0-9-]*(?:\s|$)/.test(line.trim()))
|
|
87
56
|
.join('\n')
|
|
88
57
|
.trim();
|
|
89
58
|
return {
|
|
90
59
|
description: description.length > 0 ? description : null,
|
|
91
60
|
tags,
|
|
92
|
-
params,
|
|
93
|
-
returns,
|
|
94
61
|
};
|
|
95
62
|
}
|
|
63
|
+
/***
|
|
64
|
+
* Removes Paradox comment delimiters while preserving prose content.
|
|
65
|
+
*/
|
|
96
66
|
function normalizeDocBlock(raw) {
|
|
97
67
|
return raw
|
|
98
68
|
.replace(/^\/\*\*\*/, '')
|
|
@@ -226,9 +226,7 @@ function collectMembersFromType(program, reference, inheritedFrom, depth) {
|
|
|
226
226
|
return [];
|
|
227
227
|
const propertyType = property.getTypeAtLocation(declaration);
|
|
228
228
|
const rawComment = getParadoxComment(declaration);
|
|
229
|
-
const parsed = rawComment
|
|
230
|
-
? parseParadoxComment(rawComment)
|
|
231
|
-
: { description: null, isConfig: false, params: {}, returns: null };
|
|
229
|
+
const parsed = rawComment ? parseParadoxComment(rawComment) : { description: null };
|
|
232
230
|
const required = !property.isOptional() && !isUndefinedUnion(propertyType);
|
|
233
231
|
const childReference = shouldExpandType(program, propertyType)
|
|
234
232
|
? resolveTypeReference(program, propertyType)
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import type { Node } from 'ts-morph';
|
|
2
|
+
export { parseParadoxComment } from '../utils/parseParadoxComment.js';
|
|
2
3
|
/***
|
|
3
4
|
* Reads the nearest Paradox doc comment attached to a declaration.
|
|
4
5
|
*/
|
|
5
6
|
export declare function getParadoxComment(node: Node | undefined): string | null;
|
|
6
|
-
interface ParsedParadoxComment {
|
|
7
|
-
description: string | null;
|
|
8
|
-
isConfig: boolean;
|
|
9
|
-
params: Record<string, string>;
|
|
10
|
-
returns: string | null;
|
|
11
|
-
}
|
|
12
|
-
/***
|
|
13
|
-
* Parses a Paradox doc comment into structured metadata.
|
|
14
|
-
*/
|
|
15
|
-
export declare function parseParadoxComment(rawComment: string): ParsedParadoxComment;
|
|
16
|
-
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { parseParadoxComment } from '../utils/parseParadoxComment.js';
|
|
1
2
|
/***
|
|
2
3
|
* Reads the nearest Paradox doc comment attached to a declaration.
|
|
3
4
|
*/
|
|
@@ -19,46 +20,3 @@ export function getParadoxComment(node) {
|
|
|
19
20
|
return null;
|
|
20
21
|
return text.slice(commentStart, commentEnd + 2);
|
|
21
22
|
}
|
|
22
|
-
/***
|
|
23
|
-
* Parses a Paradox doc comment into structured metadata.
|
|
24
|
-
*/
|
|
25
|
-
export function parseParadoxComment(rawComment) {
|
|
26
|
-
const lines = rawComment
|
|
27
|
-
.replace(/^\/\*\*\*/, '')
|
|
28
|
-
.replace(/\*\/$/, '')
|
|
29
|
-
.split('\n')
|
|
30
|
-
.map((line) => line.replace(/^\s*\*\s?/, '').trimEnd());
|
|
31
|
-
let isConfig = false;
|
|
32
|
-
const params = {};
|
|
33
|
-
let returns = null;
|
|
34
|
-
const description = lines
|
|
35
|
-
.filter((line) => {
|
|
36
|
-
const trimmed = line.trimStart();
|
|
37
|
-
if (trimmed.startsWith('@config')) {
|
|
38
|
-
isConfig = true;
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
if (trimmed.startsWith('@param ')) {
|
|
42
|
-
const paramBody = trimmed.slice('@param '.length).trim();
|
|
43
|
-
const [name, ...descriptionParts] = paramBody.split(/\s+/);
|
|
44
|
-
if (name) {
|
|
45
|
-
params[name] = descriptionParts.join(' ').trim();
|
|
46
|
-
}
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
if (trimmed.startsWith('@returns') || trimmed.startsWith('@return')) {
|
|
50
|
-
const returnBody = trimmed.replace(/^@returns?/, '').trim();
|
|
51
|
-
returns = returnBody.length > 0 ? returnBody : null;
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
return true;
|
|
55
|
-
})
|
|
56
|
-
.join('\n')
|
|
57
|
-
.trim();
|
|
58
|
-
return {
|
|
59
|
-
description: description.length > 0 ? description : null,
|
|
60
|
-
isConfig,
|
|
61
|
-
params,
|
|
62
|
-
returns,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { DOCUMENTATION_POLICY } from '@ankhorage/policy/documentation';
|
|
2
|
+
export const defaultTagRegistry = new Set(DOCUMENTATION_POLICY.tags.map((tag) => tag.name));
|
|
@@ -43,12 +43,12 @@ function createSourceFunction(name, node, root) {
|
|
|
43
43
|
const sourceFile = node.getSourceFile();
|
|
44
44
|
const { column, line } = sourceFile.getLineAndColumnAtPos(node.getStart(false));
|
|
45
45
|
const rawComment = getParadoxComment(node);
|
|
46
|
-
const parsedComment = rawComment
|
|
47
|
-
? parseParadoxComment(rawComment)
|
|
48
|
-
: { description: null, isReadme: false };
|
|
46
|
+
const parsedComment = rawComment === null ? null : parseParadoxComment(rawComment);
|
|
49
47
|
return {
|
|
50
48
|
name,
|
|
51
|
-
description: parsedComment
|
|
49
|
+
description: parsedComment?.description ?? null,
|
|
50
|
+
see: parsedComment?.see ?? [],
|
|
51
|
+
security: parsedComment?.security ?? [],
|
|
52
52
|
sourceLocation: {
|
|
53
53
|
filePath: toPosixPath(relative(root, sourceFile.getFilePath())),
|
|
54
54
|
line,
|
package/dist/analyze/types.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
+
import type { PolicySeverity } from '@ankhorage/policy/status';
|
|
1
2
|
import type { Node } from 'ts-morph';
|
|
2
|
-
interface AnalysisExample {
|
|
3
|
-
title: string | null;
|
|
4
|
-
language: string | null;
|
|
5
|
-
code: string;
|
|
6
|
-
}
|
|
7
3
|
/***
|
|
8
4
|
* Describes one exported declaration discovered in a package.
|
|
9
5
|
*/
|
|
@@ -40,9 +36,11 @@ export interface AnalysisStructuredRow {
|
|
|
40
36
|
export interface AnalysisExport {
|
|
41
37
|
name: string;
|
|
42
38
|
node: Node;
|
|
39
|
+
title: string | null;
|
|
43
40
|
description: string | null;
|
|
44
41
|
isReadme: boolean;
|
|
45
|
-
|
|
42
|
+
see: string[];
|
|
43
|
+
security: string[];
|
|
46
44
|
kind: 'function' | 'type' | 'value' | 'unknown';
|
|
47
45
|
modulePath: string;
|
|
48
46
|
sourceLocation: AnalysisSourceLocation;
|
|
@@ -53,13 +51,14 @@ export interface AnalysisExport {
|
|
|
53
51
|
structuredRows: AnalysisStructuredRow[];
|
|
54
52
|
}
|
|
55
53
|
/***
|
|
56
|
-
* Describes one React component and its
|
|
54
|
+
* Describes one React component and its props.
|
|
57
55
|
*/
|
|
58
56
|
export interface AnalysisComponent {
|
|
59
57
|
name: string;
|
|
60
58
|
description: string | null;
|
|
61
59
|
isReadme: boolean;
|
|
62
|
-
|
|
60
|
+
see: string[];
|
|
61
|
+
security: string[];
|
|
63
62
|
modulePath: string;
|
|
64
63
|
sourceLocation: AnalysisSourceLocation;
|
|
65
64
|
exportPaths: string[];
|
|
@@ -73,28 +72,30 @@ export interface AnalysisComponent {
|
|
|
73
72
|
}
|
|
74
73
|
export interface AnalysisUsage {
|
|
75
74
|
packageName: string;
|
|
76
|
-
commands: AnalysisUsageCommand[];
|
|
77
|
-
}
|
|
78
|
-
interface AnalysisDonation {
|
|
79
|
-
account: string;
|
|
80
|
-
}
|
|
81
|
-
interface AnalysisUsageCommand {
|
|
82
|
-
name: string;
|
|
83
75
|
command: string;
|
|
84
76
|
}
|
|
85
|
-
interface
|
|
77
|
+
export interface AnalysisUsageEntry {
|
|
78
|
+
area: 'cli' | 'examples';
|
|
86
79
|
title: string | null;
|
|
87
80
|
description: string | null;
|
|
88
81
|
language: string;
|
|
89
82
|
code: string;
|
|
90
83
|
sourcePath: string;
|
|
84
|
+
isReadme: boolean;
|
|
85
|
+
see: string[];
|
|
86
|
+
security: string[];
|
|
91
87
|
}
|
|
92
|
-
interface
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
export interface AnalysisDocumentationFinding {
|
|
89
|
+
ruleId: string;
|
|
90
|
+
severity: PolicySeverity;
|
|
91
|
+
message: string;
|
|
92
|
+
sourcePath: string | null;
|
|
93
|
+
line: number | null;
|
|
94
|
+
}
|
|
95
|
+
interface AnalysisDonation {
|
|
96
|
+
account: string;
|
|
95
97
|
}
|
|
96
98
|
interface AnalysisReadmeConfig {
|
|
97
|
-
description: string | null;
|
|
98
99
|
language: string;
|
|
99
100
|
code: string;
|
|
100
101
|
sourcePath: string;
|
|
@@ -122,6 +123,8 @@ export interface AnalysisSequenceScenario {
|
|
|
122
123
|
export interface AnalysisSourceFunction {
|
|
123
124
|
name: string;
|
|
124
125
|
description: string | null;
|
|
126
|
+
see: string[];
|
|
127
|
+
security: string[];
|
|
125
128
|
sourceLocation: AnalysisSourceLocation;
|
|
126
129
|
}
|
|
127
130
|
interface AnalysisTypeMember {
|
|
@@ -177,14 +180,18 @@ export interface AnalysisResult {
|
|
|
177
180
|
modules: AnalysisModule[];
|
|
178
181
|
badges: AnalysisBadge[];
|
|
179
182
|
sequenceScenarios: AnalysisSequenceScenario[];
|
|
180
|
-
usage: AnalysisUsage
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
usage: AnalysisUsage;
|
|
184
|
+
usageEntries: AnalysisUsageEntry[];
|
|
185
|
+
exampleCount: number;
|
|
186
|
+
findings: AnalysisDocumentationFinding[];
|
|
184
187
|
readmeConfig: AnalysisReadmeConfig | null;
|
|
185
188
|
config: {
|
|
186
189
|
exportName: string;
|
|
190
|
+
title: string | null;
|
|
191
|
+
description: string | null;
|
|
187
192
|
isReadme: boolean;
|
|
193
|
+
see: string[];
|
|
194
|
+
security: string[];
|
|
188
195
|
members: AnalysisTypeMember[];
|
|
189
196
|
} | null;
|
|
190
197
|
graphs: AnalysisGraphs;
|
package/dist/analyze/usage.d.ts
CHANGED
|
@@ -11,6 +11,6 @@ export interface PackageJsonModel {
|
|
|
11
11
|
prettier?: unknown;
|
|
12
12
|
}
|
|
13
13
|
/***
|
|
14
|
-
* Builds
|
|
14
|
+
* Builds the canonical Ankh package help command from package metadata.
|
|
15
15
|
*/
|
|
16
|
-
export declare function createUsageFromPackageJson(pkg: PackageJsonModel): AnalysisUsage
|
|
16
|
+
export declare function createUsageFromPackageJson(pkg: PackageJsonModel): AnalysisUsage;
|
package/dist/analyze/usage.js
CHANGED
|
@@ -1,41 +1,11 @@
|
|
|
1
1
|
/***
|
|
2
|
-
* Builds
|
|
2
|
+
* Builds the canonical Ankh package help command from package metadata.
|
|
3
3
|
*/
|
|
4
4
|
export function createUsageFromPackageJson(pkg) {
|
|
5
|
-
|
|
6
|
-
return null;
|
|
7
|
-
if (typeof pkg.bin === 'string') {
|
|
8
|
-
return {
|
|
9
|
-
packageName: pkg.name,
|
|
10
|
-
commands: [
|
|
11
|
-
{
|
|
12
|
-
name: getPackageBaseName(pkg.name),
|
|
13
|
-
command: `bunx ${pkg.name}`,
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const entries = Object.keys(pkg.bin).sort((a, b) => a.localeCompare(b));
|
|
19
|
-
if (entries.length === 0)
|
|
20
|
-
return null;
|
|
21
|
-
if (entries.length === 1) {
|
|
22
|
-
const [name] = entries;
|
|
23
|
-
return {
|
|
24
|
-
packageName: pkg.name,
|
|
25
|
-
commands: [
|
|
26
|
-
{
|
|
27
|
-
name,
|
|
28
|
-
command: `bunx ${pkg.name}`,
|
|
29
|
-
},
|
|
30
|
-
],
|
|
31
|
-
};
|
|
32
|
-
}
|
|
5
|
+
const packageName = getPackageBaseName(pkg.name);
|
|
33
6
|
return {
|
|
34
7
|
packageName: pkg.name,
|
|
35
|
-
|
|
36
|
-
name,
|
|
37
|
-
command: `bunx ${pkg.name} ${name}`,
|
|
38
|
-
})),
|
|
8
|
+
command: `ankh ${packageName} --help`,
|
|
39
9
|
};
|
|
40
10
|
}
|
|
41
11
|
/***
|
|
@@ -42,8 +42,7 @@ function getSourceLocation(node, root) {
|
|
|
42
42
|
* Extracts callable signatures for exported functions and callable values.
|
|
43
43
|
*/
|
|
44
44
|
function getSignatures(symbol, node, root) {
|
|
45
|
-
const
|
|
46
|
-
const signatures = getCallableDeclarations(symbol, node).map((declaration) => getSignature(declaration, parsed.params, parsed.returns, root));
|
|
45
|
+
const signatures = getCallableDeclarations(symbol, node).map((declaration) => getSignature(declaration, root));
|
|
47
46
|
return uniqueBy(signatures.filter((signature) => signature.parameters.length > 0 ||
|
|
48
47
|
signature.returnType !== null ||
|
|
49
48
|
signature.returnDescription !== null), (signature) => signature.label);
|
|
@@ -51,16 +50,13 @@ function getSignatures(symbol, node, root) {
|
|
|
51
50
|
/***
|
|
52
51
|
* Builds one normalized call signature from a callable declaration.
|
|
53
52
|
*/
|
|
54
|
-
function getSignature(declaration,
|
|
55
|
-
const normalizedParameters = declaration.getParameters().map((parameter) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
description: parameterDescription ? parameterDescription.trim() : null,
|
|
62
|
-
};
|
|
63
|
-
});
|
|
53
|
+
function getSignature(declaration, root) {
|
|
54
|
+
const normalizedParameters = declaration.getParameters().map((parameter) => ({
|
|
55
|
+
name: parameter.getName(),
|
|
56
|
+
type: normalizeTypeText(parameter.getType().getText(parameter), root),
|
|
57
|
+
required: !parameter.isOptional(),
|
|
58
|
+
description: null,
|
|
59
|
+
}));
|
|
64
60
|
const returnType = normalizeTypeText(declaration.getReturnType().getText(declaration), root);
|
|
65
61
|
const parameterLabel = normalizedParameters
|
|
66
62
|
.map((parameter) => `${parameter.name}${parameter.required ? '' : '?'}: ${parameter.type}`)
|
|
@@ -69,7 +65,7 @@ function getSignature(declaration, params, returns, root) {
|
|
|
69
65
|
label: `(${parameterLabel})${returnType === 'void' ? '' : ` => ${returnType}`}`,
|
|
70
66
|
parameters: normalizedParameters,
|
|
71
67
|
returnType,
|
|
72
|
-
returnDescription:
|
|
68
|
+
returnDescription: null,
|
|
73
69
|
};
|
|
74
70
|
}
|
|
75
71
|
/***
|
|
@@ -99,23 +95,14 @@ function getMembersFromProperties(properties, root) {
|
|
|
99
95
|
return [];
|
|
100
96
|
}
|
|
101
97
|
const rawComment = getParadoxComment(declaration);
|
|
102
|
-
const parsed = rawComment
|
|
103
|
-
? parseParadoxComment(rawComment)
|
|
104
|
-
: {
|
|
105
|
-
description: null,
|
|
106
|
-
isConfig: false,
|
|
107
|
-
isReadme: false,
|
|
108
|
-
examples: [],
|
|
109
|
-
params: {},
|
|
110
|
-
returns: null,
|
|
111
|
-
};
|
|
98
|
+
const parsed = rawComment === null ? null : parseParadoxComment(rawComment);
|
|
112
99
|
return [
|
|
113
100
|
{
|
|
114
101
|
name: property.getName(),
|
|
115
102
|
kind: isMemberMethodDeclaration(declaration) ? 'method' : 'property',
|
|
116
103
|
type: normalizeTypeText(property.getTypeAtLocation(declaration).getText(declaration), root),
|
|
117
104
|
required: !property.isOptional(),
|
|
118
|
-
description: parsed
|
|
105
|
+
description: parsed?.description ?? null,
|
|
119
106
|
},
|
|
120
107
|
];
|
|
121
108
|
});
|
|
@@ -256,22 +243,6 @@ function getCallableNode(node) {
|
|
|
256
243
|
function isMemberMethodDeclaration(node) {
|
|
257
244
|
return Node.isMethodDeclaration(node) || Node.isMethodSignature(node);
|
|
258
245
|
}
|
|
259
|
-
/***
|
|
260
|
-
* Reads Paradox comment metadata from a declaration.
|
|
261
|
-
*/
|
|
262
|
-
function readParadoxMetadata(node) {
|
|
263
|
-
const rawComment = getParadoxComment(node);
|
|
264
|
-
return rawComment
|
|
265
|
-
? parseParadoxComment(rawComment)
|
|
266
|
-
: {
|
|
267
|
-
description: null,
|
|
268
|
-
isConfig: false,
|
|
269
|
-
isReadme: false,
|
|
270
|
-
examples: [],
|
|
271
|
-
params: {},
|
|
272
|
-
returns: null,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
246
|
/***
|
|
276
247
|
* Finds related exported symbols mentioned in signature and member type text.
|
|
277
248
|
*/
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
+
import { type ParadoxDocTagName } from '../../doc-tags/registry.js';
|
|
2
|
+
interface ParsedParadoxTag {
|
|
3
|
+
readonly name: ParadoxDocTagName;
|
|
4
|
+
readonly value: string | null;
|
|
5
|
+
}
|
|
1
6
|
/***
|
|
2
|
-
* Parsed representation of a Paradox
|
|
7
|
+
* Parsed representation of a Paradox documentation comment.
|
|
3
8
|
*/
|
|
4
9
|
export interface ParsedParadoxComment {
|
|
5
10
|
description: string | null;
|
|
6
11
|
isConfig: boolean;
|
|
7
12
|
isReadme: boolean;
|
|
8
13
|
isUsage: boolean;
|
|
9
|
-
examples: ParsedExample[];
|
|
10
|
-
params: Record<string, string>;
|
|
11
|
-
returns: string | null;
|
|
12
|
-
}
|
|
13
|
-
interface ParsedExample {
|
|
14
14
|
title: string | null;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
see: string[];
|
|
16
|
+
security: string[];
|
|
17
|
+
tags: ParsedParadoxTag[];
|
|
18
|
+
unsupportedTags: string[];
|
|
19
|
+
hasCodeBlock: boolean;
|
|
17
20
|
}
|
|
18
21
|
/***
|
|
19
|
-
* Parses a Paradox
|
|
22
|
+
* Parses a Paradox comment into prose, supported tags, and validation evidence.
|
|
20
23
|
*/
|
|
21
24
|
export declare function parseParadoxComment(rawComment: string): ParsedParadoxComment;
|
|
22
25
|
export {};
|
|
@@ -1,97 +1,85 @@
|
|
|
1
|
-
|
|
1
|
+
import { isParadoxDocTagName } from '../../doc-tags/registry.js';
|
|
2
2
|
/***
|
|
3
|
-
* Parses a Paradox
|
|
3
|
+
* Parses a Paradox comment into prose, supported tags, and validation evidence.
|
|
4
4
|
*/
|
|
5
5
|
export function parseParadoxComment(rawComment) {
|
|
6
6
|
const lines = normalizeCommentLines(rawComment);
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const line = lines[index] ?? '';
|
|
16
|
-
const trimmed = line.trimStart();
|
|
17
|
-
if (trimmed.startsWith('@config')) {
|
|
18
|
-
isConfig = true;
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
if (trimmed.startsWith('@readme')) {
|
|
22
|
-
isReadme = true;
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
if (trimmed.startsWith(USAGE_TAG)) {
|
|
26
|
-
isUsage = true;
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (trimmed.startsWith('@example')) {
|
|
30
|
-
const parsed = parseExample(lines, index);
|
|
31
|
-
examples.push(parsed.example);
|
|
32
|
-
index = parsed.nextIndex;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (trimmed.startsWith('@param ')) {
|
|
36
|
-
const paramBody = trimmed.slice('@param '.length).trim();
|
|
37
|
-
const [name, ...descriptionParts] = paramBody.split(/\s+/);
|
|
38
|
-
if (name) {
|
|
39
|
-
params[name] = descriptionParts.join(' ').trim();
|
|
40
|
-
}
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (trimmed.startsWith('@returns') || trimmed.startsWith('@return')) {
|
|
44
|
-
const returnBody = trimmed.replace(/^@returns?/, '').trim();
|
|
45
|
-
returns = returnBody.length > 0 ? returnBody : null;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
descriptionLines.push(line);
|
|
49
|
-
}
|
|
50
|
-
const description = descriptionLines.join('\n').trim();
|
|
7
|
+
const parsedLines = lines.map(parseCommentLine);
|
|
8
|
+
const tags = parsedLines.flatMap((line) => line.tag ?? []);
|
|
9
|
+
const unsupportedTags = parsedLines.flatMap((line) => line.unsupportedTag ?? []);
|
|
10
|
+
const description = parsedLines
|
|
11
|
+
.filter((line) => line.tag === undefined && line.unsupportedTag === undefined)
|
|
12
|
+
.map((line) => line.text)
|
|
13
|
+
.join('\n')
|
|
14
|
+
.trim();
|
|
51
15
|
return {
|
|
52
16
|
description: description.length > 0 ? description : null,
|
|
53
|
-
isConfig,
|
|
54
|
-
isReadme,
|
|
55
|
-
isUsage,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
17
|
+
isConfig: hasTag(tags, 'config'),
|
|
18
|
+
isReadme: hasTag(tags, 'readme'),
|
|
19
|
+
isUsage: hasTag(tags, 'usage'),
|
|
20
|
+
title: getSingleTagValue(tags, 'title'),
|
|
21
|
+
see: getTagValues(tags, 'see'),
|
|
22
|
+
security: getTagValues(tags, 'security'),
|
|
23
|
+
tags,
|
|
24
|
+
unsupportedTags,
|
|
25
|
+
hasCodeBlock: hasCodeBlock(lines),
|
|
59
26
|
};
|
|
60
27
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
index += 1;
|
|
74
|
-
while (index < lines.length) {
|
|
75
|
-
const current = lines[index] ?? '';
|
|
76
|
-
if (current.trim() === '```')
|
|
77
|
-
break;
|
|
78
|
-
codeLines.push(current);
|
|
79
|
-
index += 1;
|
|
80
|
-
}
|
|
28
|
+
/***
|
|
29
|
+
* Parses one normalized comment line when it has explicit tag-line syntax.
|
|
30
|
+
*/
|
|
31
|
+
function parseCommentLine(text) {
|
|
32
|
+
const trimmed = text.trim();
|
|
33
|
+
const match = /^@([A-Za-z][A-Za-z0-9-]*)(?:\s+(.*))?$/.exec(trimmed);
|
|
34
|
+
if (match === null)
|
|
35
|
+
return { text };
|
|
36
|
+
const [, name] = match;
|
|
37
|
+
const value = match.slice(2).join('').trim();
|
|
38
|
+
if (!isParadoxDocTagName(name)) {
|
|
39
|
+
return { text, unsupportedTag: name };
|
|
81
40
|
}
|
|
82
41
|
return {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
42
|
+
text,
|
|
43
|
+
tag: {
|
|
44
|
+
name,
|
|
45
|
+
value: value.length > 0 ? value : null,
|
|
87
46
|
},
|
|
88
|
-
nextIndex: index,
|
|
89
47
|
};
|
|
90
48
|
}
|
|
49
|
+
/***
|
|
50
|
+
* Returns whether a parsed tag set contains the requested tag.
|
|
51
|
+
*/
|
|
52
|
+
function hasTag(tags, name) {
|
|
53
|
+
return tags.some((tag) => tag.name === name);
|
|
54
|
+
}
|
|
55
|
+
/***
|
|
56
|
+
* Returns all non-empty values for a repeatable tag.
|
|
57
|
+
*/
|
|
58
|
+
function getTagValues(tags, name) {
|
|
59
|
+
return tags.flatMap((tag) => (tag.name === name && tag.value !== null ? [tag.value] : []));
|
|
60
|
+
}
|
|
61
|
+
/***
|
|
62
|
+
* Returns the first non-empty value for a singular tag.
|
|
63
|
+
*/
|
|
64
|
+
function getSingleTagValue(tags, name) {
|
|
65
|
+
return getTagValues(tags, name)[0] ?? null;
|
|
66
|
+
}
|
|
67
|
+
/***
|
|
68
|
+
* Detects fenced or Markdown-indented code blocks while leaving inline code spans untouched.
|
|
69
|
+
*/
|
|
70
|
+
function hasCodeBlock(lines) {
|
|
71
|
+
return lines.some((line) => {
|
|
72
|
+
const trimmed = line.trimStart();
|
|
73
|
+
return trimmed.startsWith('```') || trimmed.startsWith('~~~') || /^(?: {4}|\t)\S/.test(line);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/***
|
|
77
|
+
* Removes Paradox comment syntax while preserving prose indentation for code-block validation.
|
|
78
|
+
*/
|
|
91
79
|
function normalizeCommentLines(rawComment) {
|
|
92
80
|
return rawComment
|
|
93
81
|
.replace(/^\/\*\*\*/, '')
|
|
94
82
|
.replace(/\*\/$/, '')
|
|
95
83
|
.split('\n')
|
|
96
|
-
.map((line) => line.replace(/^\s
|
|
84
|
+
.map((line) => line.replace(/^\s*\* ?/, '').replace(/\s+$/, ''));
|
|
97
85
|
}
|