-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathbuildSignature.mjs
More file actions
125 lines (106 loc) · 4.26 KB
/
buildSignature.mjs
File metadata and controls
125 lines (106 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { h as createElement } from 'hastscript';
import { highlighter } from '../../../utils/highlighter.mjs';
import createQueries from '../../../utils/queries/index.mjs';
import { parseListItem } from '../../legacy-json/utils/parseList.mjs';
import parseSignature from '../../legacy-json/utils/parseSignature.mjs';
/**
* Generates a string representation of a function or class signature.
*
* @param {string} functionName - The name of the function or class.
* @param {import('../../legacy-json/types').MethodSignature} signature - The parsed signature object.
* @param {string} prefix - Optional prefix, i.e. `'new '` for constructors.
*/
export const generateSignature = (
functionName,
{ params, return: returnType, extends: extendsType },
prefix = ''
) => {
// Class with `extends` clause
if (extendsType) {
return `class ${prefix}${functionName} extends ${extendsType.type}`;
}
// Function or method
const returnStr = returnType ? `: ${returnType.type}` : '';
const paramsStr = params
.map(param => {
let paramStr = param.name;
// Mark as optional if either optional or has a default value
if (param.optional || param.default) {
paramStr += '?';
}
return paramStr;
})
.join(', ');
return `${prefix}${functionName}(${paramsStr})${returnStr}`;
};
/**
* Creates a syntax-highlighted code block for a signature using rehype-shiki.
*
* @param {string} functionName - The function name to display.
* @param {import('../../legacy-json/types').MethodSignature} signature - Signature object with parameter and return type info.
* @param {string} prefix - Optional prefix like `'new '`.
*/
export const createSignatureCodeBlock = (functionName, signature, prefix) => {
const sig = generateSignature(functionName, signature, prefix);
const highlighted = highlighter.highlightToHast(sig, 'typescript');
return createElement('div', { class: 'signature' }, [highlighted]);
};
/**
* Infers the "real" function name from a heading node.
* Useful when auto-generated headings differ from code tokens.
*
* @param {HeadingMetadataEntry} heading - Metadata with name and text fields.
* @param {any} fallback - Fallback value if inference fails.
*/
export const getFullName = ({ name, text }, fallback = name) => {
// If the name and text are identical, just use fallback
if (name === text) {
return fallback;
}
// Attempt to extract inline code from heading text
const code = text.trim().match(/`([^`]+)`/)?.[1];
// If inline code includes the name, return a clean version of it
return code?.includes(name)
? code
.slice(0, code.indexOf(name) + name.length) // Truncate everything after the name.
.replace(/^["']|new\s*/, '') // Strip quotes or "new" keyword
: fallback;
};
/**
* Transforms a heading + list structure into a function/class signature block.
* Mutates the `children` array by injecting the signature HAST node.
*
* @param {import('@types/mdast').Parent} parent - The parent MDAST node (usually a section).
* @param {import('@types/mdast').Heading} heading - The heading node with metadata.
* @param {number} idx - The index at which the heading occurs in `parent.children`.
*/
export default ({ children }, { data }, idx) => {
// Try to locate the parameter list immediately following the heading
const listIdx = children.findIndex(createQueries.UNIST.isTypedList);
// Parse parameters from the list, if found
const params =
listIdx >= 0 ? children[listIdx].children.map(parseListItem) : [];
// Create a parsed signature object from the heading text and list
const signature = parseSignature(data.text, params);
if (data.type === 'class' && !signature.extends) {
// We don't need to add a signature block, since
// this class has nothing to extend.
return;
}
// Determine the displayed name (e.g., handles cases like `new Foo`)
const displayName = getFullName(data);
// If this is a class declaration, we discard the `Extends` list below it
if (data.type === 'class') {
children.splice(listIdx, 1); // Remove class param list
}
// Insert the highlighted signature block above the heading
children.splice(
idx,
0,
createSignatureCodeBlock(
displayName,
signature,
data.type === 'ctor' ? 'new ' : ''
)
);
};