-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathsignature.mjs
More file actions
146 lines (125 loc) · 4.92 KB
/
signature.mjs
File metadata and controls
146 lines (125 loc) · 4.92 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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { h as createElement } from 'hastscript';
import { createJSXElement } from './ast.mjs';
import { parseListIntoProperties } from './types.mjs';
import { highlighter } from '../../../utils/highlighter.mjs';
import { UNIST } from '../../../utils/queries/index.mjs';
import { parseListItem } from '../../legacy-json/utils/parseList.mjs';
import parseSignature from '../../legacy-json/utils/parseSignature.mjs';
import { JSX_IMPORTS } from '../../web/constants.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}` : ': void')
.split('|')
.map(part => part.trim())
.filter(Boolean)
.join(' | ');
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 {import('../../metadata/types').HeadingData} 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*/g, '') // 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('../../metadata/types').HeadingNode} heading - The heading node with metadata.
* @param {number} idx - The index at which the heading occurs in `parent.children`.
*/
export const insertSignatureCodeBlock = ({ children }, { data }, idx) => {
// Try to locate the parameter list immediately following the heading
const listIdx = children.findIndex(UNIST.isStronglyTypedList);
// 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 ' : ''
)
);
};
/**
* Renders a table of properties based on parsed metadata from a Markdown list.
*
* @param {import('mdast').List} node
*/
export const createSignatureTable = node => {
const items = parseListIntoProperties(node);
return createJSXElement(JSX_IMPORTS.FunctionSignature.name, {
title: items.length === 1 && 'kind' in items[0] ? null : 'Attributes',
items,
});
};