@ankhorage/paradox 0.1.27 → 0.2.1
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 +14 -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 +17 -15
- package/dist/analyze/modules.js +3 -8
- 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 +3 -7
- 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/sequenceScenarios.js +2 -4
- 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 +3 -11
- 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
|
@@ -1,60 +1,108 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { extname,
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { extname, join, relative } from 'node:path';
|
|
3
|
+
import { DOCUMENTATION_POLICY } from '@ankhorage/policy/documentation';
|
|
4
|
+
import { Project } from 'ts-morph';
|
|
5
|
+
import { getParadoxComment } from './utils/getParadoxComment.js';
|
|
3
6
|
import { parseParadoxComment } from './utils/parseParadoxComment.js';
|
|
4
|
-
const
|
|
7
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
|
|
5
8
|
/***
|
|
6
|
-
* Collects
|
|
9
|
+
* Collects every canonical usage declaration from examples and CLI source roots.
|
|
7
10
|
*/
|
|
8
11
|
export async function analyzeReadmeUsage(options) {
|
|
9
|
-
const
|
|
10
|
-
|
|
12
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true });
|
|
13
|
+
const files = (await Promise.all(DOCUMENTATION_POLICY.paths.usageRoots.map((usageRoot) => collectSourceFilesAsync(join(options.root, usageRoot)))))
|
|
14
|
+
.flat()
|
|
15
|
+
.sort((left, right) => left.localeCompare(right));
|
|
16
|
+
return files.flatMap((filePath) => analyzeUsageFile(options.root, project, filePath));
|
|
11
17
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
/***
|
|
19
|
+
* Collects supported source files recursively below one canonical usage root.
|
|
20
|
+
*/
|
|
21
|
+
async function collectSourceFilesAsync(root) {
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (isMissingPathError(error))
|
|
28
|
+
return [];
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
const nested = await Promise.all(entries.map(async (entry) => {
|
|
32
|
+
const path = join(root, entry.name);
|
|
33
|
+
if (entry.isDirectory())
|
|
34
|
+
return collectSourceFilesAsync(path);
|
|
35
|
+
return entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name)) ? [path] : [];
|
|
36
|
+
}));
|
|
37
|
+
return nested.flat();
|
|
38
|
+
}
|
|
39
|
+
/***
|
|
40
|
+
* Extracts usage-marked top-level statements from one real source file.
|
|
41
|
+
*/
|
|
42
|
+
function analyzeUsageFile(root, project, filePath) {
|
|
43
|
+
const sourceFile = project.getSourceFile(filePath) ?? project.addSourceFileAtPath(filePath);
|
|
44
|
+
const sourcePath = toPosixPath(relative(root, filePath));
|
|
45
|
+
return sourceFile.getStatements().flatMap((statement) => {
|
|
46
|
+
const comment = getParadoxComment(statement);
|
|
47
|
+
if (comment === null)
|
|
48
|
+
return [];
|
|
49
|
+
const parsed = parseParadoxComment(comment);
|
|
50
|
+
if (!parsed.isUsage)
|
|
51
|
+
return [];
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
area: sourcePath.startsWith(`${DOCUMENTATION_POLICY.paths.examplesRoot}/`)
|
|
55
|
+
? 'examples'
|
|
56
|
+
: 'cli',
|
|
57
|
+
title: parsed.title ?? deriveUsageTitle(sourcePath),
|
|
58
|
+
description: parsed.description,
|
|
59
|
+
language: getLanguage(sourcePath),
|
|
60
|
+
code: getStatementCode(statement),
|
|
61
|
+
sourcePath,
|
|
62
|
+
isReadme: parsed.isReadme,
|
|
63
|
+
see: parsed.see,
|
|
64
|
+
security: parsed.security,
|
|
65
|
+
},
|
|
66
|
+
];
|
|
26
67
|
});
|
|
27
68
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (!comment.includes(USAGE_TAG))
|
|
34
|
-
continue;
|
|
35
|
-
matches.push({
|
|
36
|
-
comment,
|
|
37
|
-
start: match.index,
|
|
38
|
-
end: match.index + comment.length,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return matches;
|
|
69
|
+
/***
|
|
70
|
+
* Returns the exact source statement owned by a usage comment.
|
|
71
|
+
*/
|
|
72
|
+
function getStatementCode(statement) {
|
|
73
|
+
return statement.getText().trim();
|
|
42
74
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
return
|
|
50
|
-
|
|
75
|
+
/***
|
|
76
|
+
* Derives a deterministic title from the canonical example or CLI structure.
|
|
77
|
+
*/
|
|
78
|
+
function deriveUsageTitle(sourcePath) {
|
|
79
|
+
const parts = sourcePath.split('/');
|
|
80
|
+
if (parts[0] === DOCUMENTATION_POLICY.paths.examplesRoot) {
|
|
81
|
+
return titleCase(parts[1] ?? 'usage');
|
|
82
|
+
}
|
|
83
|
+
if (sourcePath === `${DOCUMENTATION_POLICY.paths.cliRoot}/index.ts`) {
|
|
84
|
+
return 'CLI';
|
|
85
|
+
}
|
|
86
|
+
const commandsIndex = parts.indexOf('commands');
|
|
87
|
+
const commandParts = commandsIndex === -1 ? [parts.at(-1) ?? 'cli'] : parts.slice(commandsIndex + 1);
|
|
88
|
+
return commandParts
|
|
89
|
+
.map((part) => part.replace(/\.[^.]+$/, ''))
|
|
90
|
+
.map(titleCase)
|
|
91
|
+
.join(' ');
|
|
51
92
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return
|
|
93
|
+
/***
|
|
94
|
+
* Converts a kebab-case structural segment into presentation words.
|
|
95
|
+
*/
|
|
96
|
+
function titleCase(value) {
|
|
97
|
+
return value
|
|
98
|
+
.split('-')
|
|
99
|
+
.filter((word) => word.length > 0)
|
|
100
|
+
.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`)
|
|
101
|
+
.join(' ');
|
|
57
102
|
}
|
|
103
|
+
/***
|
|
104
|
+
* Returns the Markdown fence language for a usage source path.
|
|
105
|
+
*/
|
|
58
106
|
function getLanguage(sourcePath) {
|
|
59
107
|
const extension = extname(sourcePath).toLowerCase();
|
|
60
108
|
if (extension === '.tsx')
|
|
@@ -63,10 +111,38 @@ function getLanguage(sourcePath) {
|
|
|
63
111
|
return 'ts';
|
|
64
112
|
if (extension === '.jsx')
|
|
65
113
|
return 'jsx';
|
|
66
|
-
if (extension === '.js')
|
|
114
|
+
if (extension === '.js' || extension === '.mjs' || extension === '.cjs')
|
|
67
115
|
return 'js';
|
|
68
116
|
return '';
|
|
69
117
|
}
|
|
118
|
+
/***
|
|
119
|
+
* Counts real example directories directly below the canonical examples root.
|
|
120
|
+
*/
|
|
121
|
+
export async function countExampleDirectoriesAsync(root) {
|
|
122
|
+
try {
|
|
123
|
+
const entries = await readdir(join(root, DOCUMENTATION_POLICY.paths.examplesRoot), {
|
|
124
|
+
withFileTypes: true,
|
|
125
|
+
});
|
|
126
|
+
return entries.filter((entry) => entry.isDirectory()).length;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (isMissingPathError(error))
|
|
130
|
+
return 0;
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/***
|
|
135
|
+
* Checks whether a filesystem error reports a missing path.
|
|
136
|
+
*/
|
|
137
|
+
function isMissingPathError(error) {
|
|
138
|
+
return (error instanceof Error &&
|
|
139
|
+
'code' in error &&
|
|
140
|
+
typeof error.code === 'string' &&
|
|
141
|
+
error.code === 'ENOENT');
|
|
142
|
+
}
|
|
143
|
+
/***
|
|
144
|
+
* Normalizes filesystem separators for stable documentation paths.
|
|
145
|
+
*/
|
|
70
146
|
function toPosixPath(path) {
|
|
71
147
|
return path.replaceAll('\\', '/');
|
|
72
148
|
}
|
|
@@ -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(/^\/\*\*\*/, '')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { uniqueSortedStrings } from '@ankhorage/utility/array';
|
|
1
2
|
import { Node as MorphNode, TypeFormatFlags } from 'ts-morph';
|
|
2
3
|
import { isReactComponent } from './isReactComponent.js';
|
|
3
4
|
import { getParadoxComment, parseParadoxComment } from './paradoxComment.js';
|
|
@@ -21,7 +22,7 @@ export function collectExports(program) {
|
|
|
21
22
|
const sourcePath = relativeToRoot(program.root, declaration.getSourceFile().getFilePath());
|
|
22
23
|
const existing = exportsByName.get(name);
|
|
23
24
|
const exportPaths = existing
|
|
24
|
-
?
|
|
25
|
+
? uniqueSortedStrings([...existing.exportPaths, entrypointPath])
|
|
25
26
|
: [entrypointPath];
|
|
26
27
|
const kind = existing?.kind ?? detectExportKind(declaration);
|
|
27
28
|
exportsByName.set(name, {
|
|
@@ -226,9 +227,7 @@ function collectMembersFromType(program, reference, inheritedFrom, depth) {
|
|
|
226
227
|
return [];
|
|
227
228
|
const propertyType = property.getTypeAtLocation(declaration);
|
|
228
229
|
const rawComment = getParadoxComment(declaration);
|
|
229
|
-
const parsed = rawComment
|
|
230
|
-
? parseParadoxComment(rawComment)
|
|
231
|
-
: { description: null, isConfig: false, params: {}, returns: null };
|
|
230
|
+
const parsed = rawComment ? parseParadoxComment(rawComment) : { description: null };
|
|
232
231
|
const required = !property.isOptional() && !isUndefinedUnion(propertyType);
|
|
233
232
|
const childReference = shouldExpandType(program, propertyType)
|
|
234
233
|
? resolveTypeReference(program, propertyType)
|
|
@@ -380,9 +379,6 @@ function getCallableNode(node) {
|
|
|
380
379
|
}
|
|
381
380
|
return null;
|
|
382
381
|
}
|
|
383
|
-
function uniqueSorted(values) {
|
|
384
|
-
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
385
|
-
}
|
|
386
382
|
const IGNORED_RELATED_SYMBOLS = new Set([
|
|
387
383
|
'Array',
|
|
388
384
|
'Boolean',
|
|
@@ -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));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAbsolute, join, normalize } from 'node:path';
|
|
2
|
+
import { uniqueSortedStrings } from '@ankhorage/utility/array';
|
|
2
3
|
import { Node as MorphNode, } from 'ts-morph';
|
|
3
4
|
import { relativeToRoot, toPosixPath } from './semantic/utils.js';
|
|
4
5
|
import { getParadoxComment } from './utils/getParadoxComment.js';
|
|
@@ -85,7 +86,7 @@ function getBinSourceCandidates(targetPath) {
|
|
|
85
86
|
candidates.push(normalized.replace(/\.jsx?$/, '.ts'));
|
|
86
87
|
candidates.push(normalized.replace(/\.jsx?$/, '.tsx'));
|
|
87
88
|
}
|
|
88
|
-
return
|
|
89
|
+
return uniqueSortedStrings(candidates);
|
|
89
90
|
}
|
|
90
91
|
function getSourceFileByRelativePath(project, root, relativePath) {
|
|
91
92
|
const absolutePath = normalize(isAbsolute(relativePath) ? relativePath : join(root, relativePath));
|
|
@@ -169,6 +170,3 @@ function uniqueByFunctionName(declarations) {
|
|
|
169
170
|
return true;
|
|
170
171
|
});
|
|
171
172
|
}
|
|
172
|
-
function uniqueSorted(values) {
|
|
173
|
-
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
174
|
-
}
|
|
@@ -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
|
/***
|