@patternfly/documentation-framework 2.0.0-alpha.22 → 2.0.0-alpha.24
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 +19 -0
- package/components/functionsTable/functionsTable.js +57 -0
- package/package.json +3 -2
- package/scripts/cli/cli.js +1 -0
- package/scripts/cli/generate.js +2 -2
- package/scripts/md/parseMD.js +28 -0
- package/scripts/tsDocgen.js +119 -91
- package/scripts/typeDocGen.js +209 -0
- package/scripts/writeScreenshots.js +2 -2
- package/templates/mdx.js +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 2.0.0-alpha.24 (2023-04-17)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @patternfly/documentation-framework
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 2.0.0-alpha.23 (2023-04-17)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* **docs-framework:** add support for exposing function documentation ([#3485](https://github.com/patternfly/patternfly-org/issues/3485)) ([2f19ded](https://github.com/patternfly/patternfly-org/commit/2f19deda84c75042df1870c0950bb173eb2c3d84))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
# 2.0.0-alpha.22 (2023-04-17)
|
|
7
26
|
|
|
8
27
|
**Note:** Version bump only for package @patternfly/documentation-framework
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Table,
|
|
4
|
+
Caption,
|
|
5
|
+
Thead,
|
|
6
|
+
Tr,
|
|
7
|
+
Th,
|
|
8
|
+
Tbody,
|
|
9
|
+
Td,
|
|
10
|
+
} from "@patternfly/react-table";
|
|
11
|
+
|
|
12
|
+
export const FunctionsTable = ({ functionDescriptions }) => {
|
|
13
|
+
const columnNames = {
|
|
14
|
+
name: "Function name",
|
|
15
|
+
description: "Description",
|
|
16
|
+
signature: "Type signature",
|
|
17
|
+
defaultValues: "Default parameter values",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const formatDefaultValues = (parameters) =>
|
|
21
|
+
parameters.map((param) => `${param.name}: ${param.default}; `);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Table
|
|
25
|
+
aria-label="Function documentation table"
|
|
26
|
+
variant="compact"
|
|
27
|
+
>
|
|
28
|
+
<Caption>Table showing documentation for relevant functions</Caption>
|
|
29
|
+
<Thead>
|
|
30
|
+
<Tr>
|
|
31
|
+
<Th>{columnNames.name}</Th>
|
|
32
|
+
<Th>{columnNames.signature}</Th>
|
|
33
|
+
<Th>{columnNames.defaultValues}</Th>
|
|
34
|
+
<Th>{columnNames.description}</Th>
|
|
35
|
+
</Tr>
|
|
36
|
+
</Thead>
|
|
37
|
+
<Tbody>
|
|
38
|
+
{functionDescriptions.map((functionDescription) => (
|
|
39
|
+
<Tr key={functionDescription.functionName}>
|
|
40
|
+
<Td dataLabel={columnNames.name}>
|
|
41
|
+
{functionDescription.functionName}
|
|
42
|
+
</Td>
|
|
43
|
+
<Td dataLabel={columnNames.signature}>
|
|
44
|
+
{functionDescription.type}
|
|
45
|
+
</Td>
|
|
46
|
+
<Td dataLabel={columnNames.defaultValues}>
|
|
47
|
+
{formatDefaultValues(functionDescription.params)}
|
|
48
|
+
</Td>
|
|
49
|
+
<Td dataLabel={columnNames.description}>
|
|
50
|
+
{functionDescription.description}
|
|
51
|
+
</Td>
|
|
52
|
+
</Tr>
|
|
53
|
+
))}
|
|
54
|
+
</Tbody>
|
|
55
|
+
</Table>
|
|
56
|
+
);
|
|
57
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patternfly/documentation-framework",
|
|
3
3
|
"description": "A framework to build documentation for PatternFly.",
|
|
4
|
-
"version": "2.0.0-alpha.
|
|
4
|
+
"version": "2.0.0-alpha.24",
|
|
5
5
|
"author": "Red Hat",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"sharp": "0.30.6",
|
|
70
70
|
"style-to-object": "0.3.0",
|
|
71
71
|
"to-vfile": "6.1.0",
|
|
72
|
+
"typedoc": "0.22.X",
|
|
72
73
|
"typescript": "4.3.5",
|
|
73
74
|
"unified": "9.1.0",
|
|
74
75
|
"unist-util-remove": "2.0.0",
|
|
@@ -89,5 +90,5 @@
|
|
|
89
90
|
"react": "^17.0.0 || ^18.0.0",
|
|
90
91
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
91
92
|
},
|
|
92
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "2ccf5bbcf789a71ed52c342970d63cf0b568f8f8"
|
|
93
94
|
}
|
package/scripts/cli/cli.js
CHANGED
|
@@ -45,6 +45,7 @@ program
|
|
|
45
45
|
program
|
|
46
46
|
.command('screenshots')
|
|
47
47
|
.option('-u, --urlPrefix <prefix>', 'where fullscreen pages are hosted', 'http://localhost:5000/v4')
|
|
48
|
+
.option('-a, --allRoutes', 'true if screenshots of all examples - not just full screen', false)
|
|
48
49
|
.description('updates screenshots for generated components')
|
|
49
50
|
.action(options => {
|
|
50
51
|
const { writeScreenshots } = require('../writeScreenshots');
|
package/scripts/cli/generate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const { sourceMD, sourceProps, writeIndex } = require('../md/parseMD');
|
|
2
|
+
const { sourceMD, sourceProps, sourceFunctionDocs, writeIndex } = require('../md/parseMD');
|
|
3
3
|
|
|
4
4
|
function getSource(options) {
|
|
5
5
|
return require(path.join(process.cwd(), options.parent.source));
|
|
@@ -9,7 +9,7 @@ function generate(options) {
|
|
|
9
9
|
const start = new Date();
|
|
10
10
|
console.log('write source files to patternfly-docs/generated');
|
|
11
11
|
const sourceMDWithOptions = (glob, source, ignore) => sourceMD(glob, source, ignore, options._name);
|
|
12
|
-
getSource(options)(sourceMDWithOptions, sourceProps);
|
|
12
|
+
getSource(options)(sourceMDWithOptions, sourceProps, sourceFunctionDocs);
|
|
13
13
|
const exitCode = writeIndex();
|
|
14
14
|
if (exitCode !== 0) {
|
|
15
15
|
process.exit(exitCode);
|
package/scripts/md/parseMD.js
CHANGED
|
@@ -12,10 +12,12 @@ const { typecheck } = require('./typecheck');
|
|
|
12
12
|
const { makeSlug } = require('../../helpers/slugger');
|
|
13
13
|
const { liveCodeTypes } = require('../../helpers/liveCodeTypes');
|
|
14
14
|
const { tsDocgen } = require('../tsDocgen');
|
|
15
|
+
const { getPackageFunctionDocumentation } = require("../typeDocGen");
|
|
15
16
|
|
|
16
17
|
let exitCode = 0;
|
|
17
18
|
const outputBase = path.join(process.cwd(), `patternfly-docs/generated`);
|
|
18
19
|
const tsDocs = {};
|
|
20
|
+
let functionDocs = {};
|
|
19
21
|
const routes = {};
|
|
20
22
|
const globs = {
|
|
21
23
|
props: [],
|
|
@@ -75,6 +77,24 @@ function toReactComponent(mdFilePath, source, buildMode) {
|
|
|
75
77
|
return acc;
|
|
76
78
|
}, []);
|
|
77
79
|
|
|
80
|
+
const functionDocumentation = Object.keys(
|
|
81
|
+
frontmatter.functions || {}
|
|
82
|
+
).reduce((acc, fileName) => {
|
|
83
|
+
const functionNames = frontmatter.functions[fileName];
|
|
84
|
+
|
|
85
|
+
functionNames.forEach((functionName) => {
|
|
86
|
+
if (functionDocs[fileName] && functionDocs[fileName][functionName]) {
|
|
87
|
+
const functionDescriptionWithName = { functionName, ...functionDocs[fileName][functionName]}
|
|
88
|
+
acc.push(functionDescriptionWithName);
|
|
89
|
+
} else {
|
|
90
|
+
file.message(
|
|
91
|
+
`function documentation for ${functionName} is missing from function docs generation`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return acc;
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
78
98
|
const normalizedPath = relPath
|
|
79
99
|
.replace('node_modules/@patternfly/patternfly/docs', 'src/patternfly')
|
|
80
100
|
.replace(/node_modules\/@patternfly\/react-([\w-])/, (_, match) => `packages/react-${match}`)
|
|
@@ -107,6 +127,9 @@ function toReactComponent(mdFilePath, source, buildMode) {
|
|
|
107
127
|
if (propComponents.length > 0) {
|
|
108
128
|
pageData.propComponents = propComponents;
|
|
109
129
|
}
|
|
130
|
+
if (functionDocumentation.length > 0) {
|
|
131
|
+
pageData.functionDocumentation = functionDocumentation;
|
|
132
|
+
}
|
|
110
133
|
if (frontmatter.optIn) {
|
|
111
134
|
pageData.optIn = frontmatter.optIn;
|
|
112
135
|
}
|
|
@@ -275,6 +298,10 @@ function sourceMDFile(file, source, buildMode) {
|
|
|
275
298
|
}
|
|
276
299
|
}
|
|
277
300
|
|
|
301
|
+
function sourceFunctionDocs(packageName) {
|
|
302
|
+
functionDocs = getPackageFunctionDocumentation(packageName);
|
|
303
|
+
}
|
|
304
|
+
|
|
278
305
|
function writeIndex() {
|
|
279
306
|
const stringifyRoute = ([route, pageData]) => `'${route}': {\n ${Object.entries(pageData)
|
|
280
307
|
.map(([key, val]) => `${key}: ${JSON.stringify(val)}`)
|
|
@@ -313,6 +340,7 @@ module.exports = {
|
|
|
313
340
|
globs.md.push({ glob, source, ignore });
|
|
314
341
|
sync(glob, { ignore }).forEach(file => sourceMDFile(file, source, buildMode));
|
|
315
342
|
},
|
|
343
|
+
sourceFunctionDocs,
|
|
316
344
|
writeIndex,
|
|
317
345
|
watchMD() {
|
|
318
346
|
globs.props.forEach(({ glob, ignore }) => {
|
package/scripts/tsDocgen.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const reactDocgen = require(
|
|
3
|
-
const ts = require(
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const reactDocgen = require("react-docgen");
|
|
3
|
+
const ts = require("typescript");
|
|
4
4
|
|
|
5
5
|
const annotations = [
|
|
6
6
|
{
|
|
7
7
|
regex: /@deprecated/,
|
|
8
|
-
name:
|
|
9
|
-
type:
|
|
8
|
+
name: "deprecated",
|
|
9
|
+
type: "Boolean",
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
regex: /@hide/,
|
|
13
|
-
name:
|
|
14
|
-
type:
|
|
13
|
+
name: "hide",
|
|
14
|
+
type: "Boolean",
|
|
15
15
|
},
|
|
16
16
|
{
|
|
17
17
|
regex: /@beta/,
|
|
18
|
-
name:
|
|
19
|
-
type:
|
|
18
|
+
name: "beta",
|
|
19
|
+
type: "Boolean",
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
regex: /@propType\s+(.*)/,
|
|
23
|
-
name:
|
|
24
|
-
type:
|
|
25
|
-
}
|
|
23
|
+
name: "type",
|
|
24
|
+
type: "String",
|
|
25
|
+
},
|
|
26
26
|
];
|
|
27
27
|
|
|
28
28
|
function addAnnotations(prop) {
|
|
@@ -30,12 +30,12 @@ function addAnnotations(prop) {
|
|
|
30
30
|
annotations.forEach(({ regex, name }) => {
|
|
31
31
|
const match = prop.description.match(regex);
|
|
32
32
|
if (match) {
|
|
33
|
-
prop.description = prop.description.replace(regex,
|
|
33
|
+
prop.description = prop.description.replace(regex, "").trim();
|
|
34
34
|
if (name) {
|
|
35
35
|
prop[name] = match[2] || match[1] || true;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
})
|
|
38
|
+
});
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return prop;
|
|
@@ -55,74 +55,101 @@ function getComponentMetadata(filename, sourceText) {
|
|
|
55
55
|
// console.warn(`No component found in ${filename}:`, err);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
return (parsedComponents || []).filter(
|
|
58
|
+
return (parsedComponents || []).filter(
|
|
59
|
+
(parsed) => parsed && parsed.displayName
|
|
60
|
+
);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
const getNodeText = (node, sourceText) => {
|
|
64
|
+
if (!node || !node.pos || !node.end) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return sourceText.substring(node.pos, node.end).trim();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const buildJsDocProps = (nodes, sourceText) =>
|
|
72
|
+
nodes?.reduce((acc, member) => {
|
|
73
|
+
const name =
|
|
74
|
+
(member.name && member.name.escapedText) ||
|
|
75
|
+
(member.parameters &&
|
|
76
|
+
`[${getNodeText(member.parameters[0], sourceText)}]`) ||
|
|
77
|
+
"Unknown";
|
|
78
|
+
acc[name] = {
|
|
79
|
+
description: member.jsDoc
|
|
80
|
+
? member.jsDoc.map((doc) => doc.comment).join("\n")
|
|
81
|
+
: null,
|
|
82
|
+
required: member.questionToken === undefined,
|
|
83
|
+
type: {
|
|
84
|
+
raw: getNodeText(member.type, sourceText).trim(),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
return acc;
|
|
88
|
+
}, {});
|
|
89
|
+
|
|
90
|
+
const getSourceFileStatements = (filename, sourceText) => {
|
|
91
|
+
const { statements } = ts.createSourceFile(
|
|
63
92
|
filename,
|
|
64
93
|
sourceText,
|
|
65
94
|
ts.ScriptTarget.Latest // languageVersion
|
|
66
95
|
);
|
|
67
|
-
|
|
68
|
-
function getText(node) {
|
|
69
|
-
if (!node || !node.pos || !node.end) {
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
72
|
-
return sourceText.substring(node.pos, node.end).trim();
|
|
73
|
-
}
|
|
74
96
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
return statements;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getInterfaceMetadata = (filename, sourceText) =>
|
|
101
|
+
getSourceFileStatements(filename, sourceText).reduce(
|
|
102
|
+
(metaDataAcc, statement) => {
|
|
103
|
+
if (statement.kind === ts.SyntaxKind.InterfaceDeclaration) {
|
|
104
|
+
metaDataAcc.push({
|
|
105
|
+
displayName: statement.name.escapedText,
|
|
106
|
+
description: statement.jsDoc?.map((doc) => doc.comment).join("\n"),
|
|
107
|
+
props: buildJsDocProps(statement.members, sourceText),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return metaDataAcc;
|
|
112
|
+
},
|
|
113
|
+
[]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const getTypeAliasMetadata = (filename, sourceText) =>
|
|
117
|
+
getSourceFileStatements(filename, sourceText).reduce(
|
|
118
|
+
(metaDataAcc, statement) => {
|
|
119
|
+
if (statement.kind === ts.SyntaxKind.TypeAliasDeclaration) {
|
|
120
|
+
const props = statement.type.types?.reduce((propAcc, type) => {
|
|
121
|
+
if (type.members) {
|
|
122
|
+
propAcc.push(buildJsDocProps(type.members, sourceText));
|
|
91
123
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
|
|
125
|
+
return propAcc;
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
metaDataAcc.push({
|
|
129
|
+
props,
|
|
130
|
+
displayName: statement.name.escapedText,
|
|
131
|
+
description: statement.jsDoc?.map((doc) => doc.comment).join("\n"),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return metaDataAcc;
|
|
136
|
+
},
|
|
137
|
+
[]
|
|
138
|
+
);
|
|
106
139
|
|
|
107
140
|
function normalizeProp([
|
|
108
141
|
name,
|
|
109
|
-
{
|
|
110
|
-
required,
|
|
111
|
-
annotatedType,
|
|
112
|
-
type,
|
|
113
|
-
tsType,
|
|
114
|
-
description,
|
|
115
|
-
defaultValue
|
|
116
|
-
}
|
|
142
|
+
{ required, annotatedType, type, tsType, description, defaultValue },
|
|
117
143
|
]) {
|
|
118
144
|
const res = {
|
|
119
145
|
name,
|
|
120
|
-
type:
|
|
121
|
-
||
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
||
|
|
125
|
-
|
|
146
|
+
type:
|
|
147
|
+
annotatedType ||
|
|
148
|
+
(type && type.name) ||
|
|
149
|
+
(type && (type.raw || type.name)) ||
|
|
150
|
+
(tsType && (tsType.raw || tsType.name)) ||
|
|
151
|
+
"No type info",
|
|
152
|
+
description,
|
|
126
153
|
};
|
|
127
154
|
if (required) {
|
|
128
155
|
res.required = true;
|
|
@@ -134,45 +161,46 @@ function normalizeProp([
|
|
|
134
161
|
return res;
|
|
135
162
|
}
|
|
136
163
|
|
|
137
|
-
function getDescription(parsed, ) {
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
164
|
function tsDocgen(file) {
|
|
142
|
-
const sourceText = fs.readFileSync(file,
|
|
165
|
+
const sourceText = fs.readFileSync(file, "utf8");
|
|
143
166
|
const componentMeta = getComponentMetadata(file, sourceText); // Array of components with props
|
|
144
167
|
const interfaceMeta = getInterfaceMetadata(file, sourceText); // Array of interfaces with props
|
|
145
|
-
const
|
|
146
|
-
|
|
168
|
+
const typeAliasMeta = getTypeAliasMetadata(file, sourceText); // Array of type aliases with props
|
|
169
|
+
const propsMetaMap = [...interfaceMeta, ...typeAliasMeta].reduce(function (
|
|
170
|
+
target,
|
|
171
|
+
interfaceOrTypeAlias
|
|
172
|
+
) {
|
|
173
|
+
target[interfaceOrTypeAlias.displayName] = interfaceOrTypeAlias;
|
|
147
174
|
return target;
|
|
148
|
-
},
|
|
175
|
+
},
|
|
176
|
+
{});
|
|
149
177
|
|
|
150
|
-
// Go through each component and check if they have an interface with a jsDoc description
|
|
178
|
+
// Go through each component and check if they have an interface or type alias with a jsDoc description
|
|
151
179
|
// If so copy it over (fix for https://github.com/patternfly/patternfly-react/issues/7612)
|
|
152
|
-
componentMeta.forEach(c => {
|
|
180
|
+
componentMeta.forEach((c) => {
|
|
153
181
|
if (c.description) {
|
|
154
182
|
return c;
|
|
155
183
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
184
|
+
|
|
185
|
+
const propsName = `${c.displayName}Props`;
|
|
186
|
+
if (propsMetaMap[propsName]?.description) {
|
|
187
|
+
c.description = propsMetaMap[propsName].description;
|
|
159
188
|
}
|
|
160
|
-
})
|
|
189
|
+
});
|
|
161
190
|
|
|
162
|
-
return componentMeta
|
|
163
|
-
|
|
164
|
-
.map(parsed => ({
|
|
191
|
+
return [...componentMeta, ...interfaceMeta, ...typeAliasMeta].map(
|
|
192
|
+
(parsed) => ({
|
|
165
193
|
name: parsed.displayName,
|
|
166
|
-
description: parsed.description ||
|
|
194
|
+
description: parsed.description || "",
|
|
167
195
|
props: Object.entries(parsed.props || {})
|
|
168
196
|
.map(normalizeProp)
|
|
169
197
|
.map(addAnnotations)
|
|
170
|
-
.filter(prop => !prop.hide)
|
|
171
|
-
.sort((p1, p2) => p1.name.localeCompare(p2.name))
|
|
172
|
-
})
|
|
198
|
+
.filter((prop) => !prop.hide)
|
|
199
|
+
.sort((p1, p2) => p1.name.localeCompare(p2.name)),
|
|
200
|
+
})
|
|
201
|
+
);
|
|
173
202
|
}
|
|
174
203
|
|
|
175
204
|
module.exports = {
|
|
176
|
-
tsDocgen
|
|
205
|
+
tsDocgen,
|
|
177
206
|
};
|
|
178
|
-
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const TypeDoc = require("typedoc");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
/** Compiles variable types if a type is generic. */
|
|
5
|
+
function getVariableType(typeArguments) {
|
|
6
|
+
if (!typeArguments || typeArguments.length === 0) {
|
|
7
|
+
return "";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const typeArgs = typeArguments.map((type) => getType(type));
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
typeArgs.reduce((acc, type) => acc + type + ", ", "<").slice(0, -2) + ">"
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** compiles key/value pairs of a Type or Interface. */
|
|
18
|
+
function getPropertyTypes(properties) {
|
|
19
|
+
const formattedProperties = properties.reduce((acc, property) => {
|
|
20
|
+
const { flags, name, type } = property;
|
|
21
|
+
const formattedName = flags.isOptional ? name + "?" : name;
|
|
22
|
+
return `${acc}${formattedName}: ${getType(type)}; `;
|
|
23
|
+
}, "");
|
|
24
|
+
|
|
25
|
+
return `{ ${formattedProperties} }`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Compiles the subtypes of a tuple. */
|
|
29
|
+
function getTupleTypes(tupleElements) {
|
|
30
|
+
const [firstType, secondType] = tupleElements.map((element) =>
|
|
31
|
+
getType(element)
|
|
32
|
+
);
|
|
33
|
+
return `[${firstType}, ${secondType}]`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Compiles the different subtypes of a union or intersection. */
|
|
37
|
+
function getJoinedTypes(types, joinChar) {
|
|
38
|
+
const joinedTypes = types.map((type) => getType(type));
|
|
39
|
+
return joinedTypes
|
|
40
|
+
.reduce((acc, type) => `${acc} ${joinChar} ${type}`, "")
|
|
41
|
+
.slice(2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Return the type of a reflection.
|
|
45
|
+
* For anything that has, or can have, multiple subtypes (i.e. variable types in a generic, properties in an interface,
|
|
46
|
+
* several types in a union, callbacks, etc.) it will recursively navigate the parsed type info as needed to resolve
|
|
47
|
+
* all relevant subtypes.
|
|
48
|
+
*/
|
|
49
|
+
function getType(reflection) {
|
|
50
|
+
switch (reflection.type) {
|
|
51
|
+
case "intrinsic":
|
|
52
|
+
case "reference":
|
|
53
|
+
return reflection.name + getVariableType(reflection.typeArguments);
|
|
54
|
+
case "union":
|
|
55
|
+
return getJoinedTypes(reflection.types, "|");
|
|
56
|
+
case "reflection":
|
|
57
|
+
const { signatures, children } = reflection.declaration;
|
|
58
|
+
if (signatures) {
|
|
59
|
+
return getFunctionDocumentation(signatures[0]).type;
|
|
60
|
+
} else if (children) {
|
|
61
|
+
return getPropertyTypes(children);
|
|
62
|
+
} else {
|
|
63
|
+
return "Props";
|
|
64
|
+
}
|
|
65
|
+
case "array":
|
|
66
|
+
return reflection.elementType.name + "[]";
|
|
67
|
+
case "tuple":
|
|
68
|
+
return getTupleTypes(reflection.elements);
|
|
69
|
+
case "intersection":
|
|
70
|
+
return getJoinedTypes(reflection.types, "&");
|
|
71
|
+
case "literal":
|
|
72
|
+
return reflection.value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Compiles information about the parameters of a function.
|
|
77
|
+
* Includes the name, default value, type, and whether it is optional.
|
|
78
|
+
*/
|
|
79
|
+
function getParamInfo(parameters = []) {
|
|
80
|
+
return parameters.map((parameter) => {
|
|
81
|
+
const { name, defaultValue, type, flags } = parameter;
|
|
82
|
+
return {
|
|
83
|
+
name,
|
|
84
|
+
default: defaultValue,
|
|
85
|
+
type: getType(type),
|
|
86
|
+
optional: flags.isOptional,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Formats call signature for a function. */
|
|
92
|
+
function formatFunctionType(parameters, returnType) {
|
|
93
|
+
const annotatedParams = parameters.map((parameter) => {
|
|
94
|
+
const { name, type, optional } = parameter;
|
|
95
|
+
|
|
96
|
+
return [optional ? name + "?" : name, type];
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const formattedParams = annotatedParams
|
|
100
|
+
.reduce((acc, param) => `${acc}, ${param[0]}: ${param[1]}`, "")
|
|
101
|
+
.slice(2);
|
|
102
|
+
|
|
103
|
+
return `(${formattedParams}) => ${returnType}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Primary entry point for gathering type information from a typedoc function reflection. */
|
|
107
|
+
function getFunctionDocumentation(callSignatureReflection) {
|
|
108
|
+
const { type, parameters, comment } = callSignatureReflection;
|
|
109
|
+
const params = getParamInfo(parameters);
|
|
110
|
+
|
|
111
|
+
const returnType = type?.name || getType(type);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
params,
|
|
115
|
+
returnType,
|
|
116
|
+
type: formatFunctionType(params, returnType),
|
|
117
|
+
description: comment?.shortText,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Parses the source for a package using TypeDoc. */
|
|
122
|
+
function typeDocGen(entryPoint, tsconfigLocation) {
|
|
123
|
+
const app = new TypeDoc.Application();
|
|
124
|
+
app.options.addReader(new TypeDoc.TSConfigReader());
|
|
125
|
+
app.bootstrap({
|
|
126
|
+
// typedoc options here
|
|
127
|
+
entryPointStrategy: "expand",
|
|
128
|
+
entryPoints: [entryPoint],
|
|
129
|
+
tsconfig: tsconfigLocation,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// this non-standard reflection creation allows us to ignore any type errors in a package
|
|
133
|
+
const project = app.converter.convert(app.getEntryPoints() ?? []);
|
|
134
|
+
|
|
135
|
+
// if something caused project conversion to fail log an error
|
|
136
|
+
if (project) {
|
|
137
|
+
return project;
|
|
138
|
+
} else {
|
|
139
|
+
console.error("Something went wrong during typedoc project conversion");
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Takes package information and returns an object with all tsdoc standard format info about the functions in it.
|
|
145
|
+
*
|
|
146
|
+
* The object has file names as keys, with values being an additional object which has the function names in that file
|
|
147
|
+
* as keys and the information about that function as the value, also in object form.
|
|
148
|
+
* Information includes the name of the file in which it's defined, the parameters, description, default values and
|
|
149
|
+
* type information.
|
|
150
|
+
*
|
|
151
|
+
* Example structure:
|
|
152
|
+
* {
|
|
153
|
+
* "components/component.tsx": {
|
|
154
|
+
* "boolToString": {
|
|
155
|
+
* "description": "Converts a boolean to a string",
|
|
156
|
+
* "params": [
|
|
157
|
+
* {
|
|
158
|
+
* "default": false,
|
|
159
|
+
* "name": "booleanValue",
|
|
160
|
+
* "optional": true,
|
|
161
|
+
* "type": "boolean",
|
|
162
|
+
* },
|
|
163
|
+
* ],
|
|
164
|
+
* "returnType": "string",
|
|
165
|
+
* "type": "(booleanValue?: boolean) => string",
|
|
166
|
+
* },
|
|
167
|
+
* },
|
|
168
|
+
* }
|
|
169
|
+
*/
|
|
170
|
+
function getPackageFunctionDocumentation(
|
|
171
|
+
packageName,
|
|
172
|
+
pathToRoot = "dist/js/index.js",
|
|
173
|
+
srcPath = "src",
|
|
174
|
+
tsconfigPath = "tsconfig.json"
|
|
175
|
+
) {
|
|
176
|
+
const packageRoot = require.resolve(packageName).replace(pathToRoot, "");
|
|
177
|
+
const entry = path.relative(process.cwd(), path.join(packageRoot, srcPath));
|
|
178
|
+
const configLocation = path.relative(
|
|
179
|
+
process.cwd(),
|
|
180
|
+
path.join(packageRoot, tsconfigPath)
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const packageReflection = typeDocGen(entry, configLocation);
|
|
184
|
+
|
|
185
|
+
// 64 corresponds with functions, see all reflection kinds here: https://typedoc.org/api/enums/Models.ReflectionKind-1.html
|
|
186
|
+
const functionReflections = packageReflection.getReflectionsByKind(64);
|
|
187
|
+
|
|
188
|
+
const functionsMap = functionReflections.reduce((acc, functionReflection) => {
|
|
189
|
+
const { name, sources, signatures } = functionReflection;
|
|
190
|
+
|
|
191
|
+
const sourceFile = sources[0].fileName;
|
|
192
|
+
const shortSourceFile = sourceFile.replace(
|
|
193
|
+
`${packageName}/${srcPath}/`,
|
|
194
|
+
""
|
|
195
|
+
);
|
|
196
|
+
const obj = getFunctionDocumentation(signatures[0]);
|
|
197
|
+
|
|
198
|
+
if (acc[shortSourceFile]) {
|
|
199
|
+
acc[shortSourceFile] = { ...acc[shortSourceFile], [name]: obj };
|
|
200
|
+
} else {
|
|
201
|
+
acc[shortSourceFile] = { [name]: obj };
|
|
202
|
+
}
|
|
203
|
+
return acc;
|
|
204
|
+
}, {});
|
|
205
|
+
|
|
206
|
+
return functionsMap;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = { getPackageFunctionDocumentation };
|
|
@@ -27,7 +27,7 @@ async function writeScreenshot({ page, data: { url, urlPrefix } }) {
|
|
|
27
27
|
await sharp(buffer).toFile(outfile);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async function writeScreenshots({ urlPrefix }) {
|
|
30
|
+
async function writeScreenshots({ urlPrefix, allRoutes }) {
|
|
31
31
|
const cluster = await Cluster.launch({
|
|
32
32
|
concurrency: Cluster.CONCURRENCY_CONTEXT,
|
|
33
33
|
maxConcurrency: os.cpus().length,
|
|
@@ -43,7 +43,7 @@ async function writeScreenshots({ urlPrefix }) {
|
|
|
43
43
|
|
|
44
44
|
// Add some pages to queue
|
|
45
45
|
Object.entries(fullscreenRoutes)
|
|
46
|
-
.filter(([, { isFullscreenOnly }]) => isFullscreenOnly)
|
|
46
|
+
.filter(([, { isFullscreenOnly }]) => allRoutes || isFullscreenOnly)
|
|
47
47
|
.forEach(([url,]) => cluster.queue({
|
|
48
48
|
url: `${urlPrefix}${url}`,
|
|
49
49
|
urlPrefix
|
package/templates/mdx.js
CHANGED
|
@@ -7,6 +7,7 @@ import { CSSVariables, PropsTable, TableOfContents, Link, AutoLinkHeader, Inline
|
|
|
7
7
|
import { capitalize, getTitle, slugger, trackEvent } from '../helpers';
|
|
8
8
|
import './mdx.css';
|
|
9
9
|
import { convertToReactComponent } from '@patternfly/ast-helpers';
|
|
10
|
+
import { FunctionsTable } from '../components/functionsTable/functionsTable';
|
|
10
11
|
|
|
11
12
|
const MDXChildTemplate = ({
|
|
12
13
|
Component,
|
|
@@ -19,7 +20,8 @@ const MDXChildTemplate = ({
|
|
|
19
20
|
sourceLink,
|
|
20
21
|
cssPrefix = [],
|
|
21
22
|
optIn,
|
|
22
|
-
beta
|
|
23
|
+
beta,
|
|
24
|
+
functionDocumentation = []
|
|
23
25
|
} = Component.getPageData();
|
|
24
26
|
const cssVarsTitle = cssPrefix.length > 0 && 'CSS variables';
|
|
25
27
|
const propsTitle = propComponents.length > 0 && 'Props';
|
|
@@ -69,6 +71,14 @@ const MDXChildTemplate = ({
|
|
|
69
71
|
<div className="ws-mdx-content-content">
|
|
70
72
|
{InlineAlerts}
|
|
71
73
|
<Component />
|
|
74
|
+
{functionDocumentation.length > 0 && (
|
|
75
|
+
<React.Fragment>
|
|
76
|
+
<AutoLinkHeader size="h2" className="ws-h2" id="functions">
|
|
77
|
+
Functions
|
|
78
|
+
</AutoLinkHeader>
|
|
79
|
+
<FunctionsTable functionDescriptions={functionDocumentation}/>
|
|
80
|
+
</React.Fragment>
|
|
81
|
+
)}
|
|
72
82
|
{propsTitle && (
|
|
73
83
|
<React.Fragment>
|
|
74
84
|
<AutoLinkHeader size="h2" className="ws-h2" id="props">
|