@hubspot/ui-extensions-sdk-api-metadata 0.11.6 → 0.12.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/README.md +43 -16
- package/dist/__generated__/component-props.js +1062 -143
- package/dist/__tests__/component-props-docs.spec.d.ts +1 -0
- package/dist/__tests__/component-props-docs.spec.js +14 -0
- package/dist/component-props-docs.d.ts +8 -0
- package/dist/component-props-docs.js +115 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal/get-type-source.d.ts +15 -0
- package/dist/internal/get-type-source.js +27 -0
- package/dist/internal/render-prop-type.d.ts +31 -0
- package/dist/internal/render-prop-type.js +213 -0
- package/dist/internal/utils/html-utils.d.ts +7 -0
- package/dist/internal/utils/html-utils.js +18 -0
- package/dist/internal/utils/jsdoc-utils.d.ts +29 -0
- package/dist/internal/utils/jsdoc-utils.js +52 -0
- package/dist/internal/utils/markdown-utils.d.ts +7 -0
- package/dist/internal/utils/markdown-utils.js +14 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.js +9 -0
- package/package.json +9 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ComponentName, getComponentPropsDocumentation } from "../index.js";
|
|
3
|
+
const snapshotsDir = path.join(__dirname, '__snapshots__');
|
|
4
|
+
const runComponentPropsSnapshotTest = (componentName) => {
|
|
5
|
+
const propsDocumentation = getComponentPropsDocumentation(componentName);
|
|
6
|
+
expect(`${JSON.stringify(propsDocumentation, null, 2)}\n`).toMatchFileSnapshot(path.join(snapshotsDir, `${componentName}.snapshot.json`));
|
|
7
|
+
};
|
|
8
|
+
describe('getComponentPropsDocumentation', () => {
|
|
9
|
+
it('should return the correct component prop documentation for the Accordion component', () => {
|
|
10
|
+
runComponentPropsSnapshotTest(ComponentName.Accordion);
|
|
11
|
+
});
|
|
12
|
+
// NOTE: We will continue to add more snapshot tests for each component so that we can verify that
|
|
13
|
+
// we are producing the correct documentation for each component.
|
|
14
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ComponentName, ComponentPropDocumentation } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Gets the documentation for the props of a component.
|
|
4
|
+
*
|
|
5
|
+
* @param componentName - The name of the component to get the props for
|
|
6
|
+
* @returns The documentation for the props of the component
|
|
7
|
+
*/
|
|
8
|
+
export declare const getComponentPropsDocumentation: (componentName: ComponentName) => ComponentPropDocumentation[];
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { isInlinedTypeReferenceNode, isObjectNode, isTypeReferenceNode, } from '@hubspot/ts-export-types-reader';
|
|
2
|
+
import { componentPropsReader } from "./__generated__/component-props.js";
|
|
3
|
+
import { renderPropTypeToHtml, } from "./internal/render-prop-type.js";
|
|
4
|
+
import { getDefaultValueFromJsdocTags, getSeeTagsFromJsdocTags, } from "./internal/utils/jsdoc-utils.js";
|
|
5
|
+
import { renderInlineMarkdownToHtml } from "./internal/utils/markdown-utils.js";
|
|
6
|
+
import { getComponentTypeSource } from "./internal/get-type-source.js";
|
|
7
|
+
/**
|
|
8
|
+
* Recursively resolves an API node to an ObjectNode if possible.
|
|
9
|
+
*
|
|
10
|
+
* Component props are typically defined as object types, but they may be wrapped
|
|
11
|
+
* in type references or inlined type references. This helper function traverses
|
|
12
|
+
* through these wrappers to find the underlying object type that contains the
|
|
13
|
+
* actual property definitions.
|
|
14
|
+
*
|
|
15
|
+
* @param apiNode - The API node to resolve (may be an object, type reference, or inlined reference)
|
|
16
|
+
* @returns The resolved ObjectNode if found, or null if the node doesn't represent an object type
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // If apiNode is a TypeReferenceNode pointing to an object type, this will resolve it
|
|
21
|
+
* const propsObject = maybeGetPropsObjectHelper(typeReferenceNode);
|
|
22
|
+
* // Returns: ObjectNode with properties array
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
const maybeGetPropsObjectHelper = (apiNode) => {
|
|
26
|
+
// If it's already an object node, return it directly
|
|
27
|
+
if (isObjectNode(apiNode)) {
|
|
28
|
+
return apiNode;
|
|
29
|
+
}
|
|
30
|
+
// If it's an inlined type reference, recursively resolve the inner type
|
|
31
|
+
if (isInlinedTypeReferenceNode(apiNode)) {
|
|
32
|
+
return maybeGetPropsObjectHelper(apiNode.type);
|
|
33
|
+
}
|
|
34
|
+
// If it's a type reference, look up the referenced type and resolve it
|
|
35
|
+
if (isTypeReferenceNode(apiNode)) {
|
|
36
|
+
const referencedNode = componentPropsReader.findReferencedTypeById(apiNode.typeId);
|
|
37
|
+
return maybeGetPropsObjectHelper(referencedNode);
|
|
38
|
+
}
|
|
39
|
+
// Not an object type or resolvable to one
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
// Matches {@link <URL> <TITLE>} or {@link <URL>}
|
|
43
|
+
const embeddedLinkTagRegex = /\{@link\s+(\S+)(?:\s+([^}]+))?\s*\}/g;
|
|
44
|
+
/**
|
|
45
|
+
* Renders a JSDoc @see tag's text content to HTML.
|
|
46
|
+
*
|
|
47
|
+
* Converts embedded `{@link}` tags in the see text to markdown link syntax,
|
|
48
|
+
* then renders the markdown to HTML.
|
|
49
|
+
*
|
|
50
|
+
* @param seeText - The text of the see tag
|
|
51
|
+
* @returns The see tag text rendered as HTML
|
|
52
|
+
*/
|
|
53
|
+
const renderSeeItemToHtml = (seeText) => {
|
|
54
|
+
// Replace all embedded {@link} tags with equivalent markdown link syntax
|
|
55
|
+
return renderInlineMarkdownToHtml(seeText.replaceAll(embeddedLinkTagRegex, (match, url, title) => {
|
|
56
|
+
return `[${title ?? url}](${url})`;
|
|
57
|
+
}));
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Gets the documentation for the props of a component.
|
|
61
|
+
*
|
|
62
|
+
* @param componentName - The name of the component to get the props for
|
|
63
|
+
* @returns The documentation for the props of the component
|
|
64
|
+
*/
|
|
65
|
+
export const getComponentPropsDocumentation = (componentName) => {
|
|
66
|
+
const { exportName, exportPath } = getComponentTypeSource(componentName);
|
|
67
|
+
const typeExport = componentPropsReader.findExportByName({
|
|
68
|
+
exportName,
|
|
69
|
+
exportPath,
|
|
70
|
+
});
|
|
71
|
+
// Validate that the type export exists
|
|
72
|
+
if (!typeExport) {
|
|
73
|
+
throw new Error(`Component types not found for component "${componentName}" (${exportName} in ${exportPath})`);
|
|
74
|
+
}
|
|
75
|
+
// Resolve the type export to an object type containing the props
|
|
76
|
+
const propsObject = maybeGetPropsObjectHelper(typeExport.type);
|
|
77
|
+
if (!propsObject) {
|
|
78
|
+
throw new Error(`Props object not found for component "${componentName}" (${exportName} in ${exportPath})`);
|
|
79
|
+
}
|
|
80
|
+
// Sort properties first by required status, then alphabetically by name for consistent output
|
|
81
|
+
const props = [...propsObject.properties].sort((a, b) => {
|
|
82
|
+
const aRequired = a.isOptional !== true;
|
|
83
|
+
const bRequired = b.isOptional !== true;
|
|
84
|
+
if (aRequired !== bRequired) {
|
|
85
|
+
return aRequired ? -1 : 1;
|
|
86
|
+
}
|
|
87
|
+
return a.name.localeCompare(b.name);
|
|
88
|
+
});
|
|
89
|
+
return props.map((prop) => {
|
|
90
|
+
const jsdocText = prop.jsdoc?.text ?? '';
|
|
91
|
+
const jsdocTags = prop.jsdoc?.tags ?? [];
|
|
92
|
+
const renderTypeContext = {
|
|
93
|
+
typesReader: componentPropsReader,
|
|
94
|
+
foundTypeIds: new Set(),
|
|
95
|
+
};
|
|
96
|
+
const typeHtml = renderPropTypeToHtml(renderTypeContext, prop.type);
|
|
97
|
+
const descriptionHtml = renderInlineMarkdownToHtml(jsdocText);
|
|
98
|
+
// Extract default value from JSDoc tags if present
|
|
99
|
+
const defaultValue = getDefaultValueFromJsdocTags(jsdocTags);
|
|
100
|
+
const seeHtmlItems = getSeeTagsFromJsdocTags(jsdocTags).map(renderSeeItemToHtml);
|
|
101
|
+
const componentProp = {
|
|
102
|
+
name: prop.name,
|
|
103
|
+
required: prop.isOptional !== true,
|
|
104
|
+
typeJsx: `<>${typeHtml}</>`,
|
|
105
|
+
descriptionJsx: `<>${descriptionHtml}</>`,
|
|
106
|
+
};
|
|
107
|
+
if (seeHtmlItems.length > 0) {
|
|
108
|
+
componentProp.seeJsxItems = seeHtmlItems.map((seeHtmlItem) => `<>${seeHtmlItem}</>`);
|
|
109
|
+
}
|
|
110
|
+
if (defaultValue != null) {
|
|
111
|
+
componentProp.defaultValueJsx = `<>${renderInlineMarkdownToHtml(defaultValue)}</>`;
|
|
112
|
+
}
|
|
113
|
+
return componentProp;
|
|
114
|
+
});
|
|
115
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ComponentName } from '../types.ts';
|
|
2
|
+
export interface ComponentSource {
|
|
3
|
+
exportName: string;
|
|
4
|
+
exportPath: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Gets the type source for a component.
|
|
8
|
+
*
|
|
9
|
+
* The component type source is the name of the export and the path the
|
|
10
|
+
* export is exported from for the `@hubspot/ui-extensions` package.
|
|
11
|
+
*
|
|
12
|
+
* @param componentName - The name of the component to get the type source for
|
|
13
|
+
* @returns The type source for the component
|
|
14
|
+
*/
|
|
15
|
+
export declare const getComponentTypeSource: (componentName: ComponentName) => ComponentSource;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lookup table for component type sources.
|
|
3
|
+
*
|
|
4
|
+
* FUTURE: We should use code generation to generate this lookup table.
|
|
5
|
+
*/
|
|
6
|
+
const componentSourceLookup = {
|
|
7
|
+
Accordion: {
|
|
8
|
+
exportName: 'AccordionProps',
|
|
9
|
+
exportPath: '.',
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Gets the type source for a component.
|
|
14
|
+
*
|
|
15
|
+
* The component type source is the name of the export and the path the
|
|
16
|
+
* export is exported from for the `@hubspot/ui-extensions` package.
|
|
17
|
+
*
|
|
18
|
+
* @param componentName - The name of the component to get the type source for
|
|
19
|
+
* @returns The type source for the component
|
|
20
|
+
*/
|
|
21
|
+
export const getComponentTypeSource = (componentName) => {
|
|
22
|
+
const componentSource = componentSourceLookup[componentName];
|
|
23
|
+
if (!componentSource) {
|
|
24
|
+
throw new Error(`Invalid component name: ${componentName}`);
|
|
25
|
+
}
|
|
26
|
+
return componentSource;
|
|
27
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AnalyzeResultReader, ApiNode } from '@hubspot/ts-export-types-reader';
|
|
2
|
+
/**
|
|
3
|
+
* Context object passed through the type rendering functions.
|
|
4
|
+
*
|
|
5
|
+
* This context maintains state needed for rendering types correctly:
|
|
6
|
+
* - typesReader: The reader for looking up referenced types
|
|
7
|
+
* - foundTypeIds: A set of type IDs that have already been rendered, used to
|
|
8
|
+
* prevent infinite recursion when rendering circular type references
|
|
9
|
+
*/
|
|
10
|
+
export interface RenderTypeContext {
|
|
11
|
+
typesReader: AnalyzeResultReader;
|
|
12
|
+
foundTypeIds: Set<string>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Renders a prop's type as an HTML string.
|
|
16
|
+
*
|
|
17
|
+
* This function handles the special case of union types in props, which need
|
|
18
|
+
* to be rendered recursively. All types are wrapped in HTML code tags for
|
|
19
|
+
* proper HTML rendering.
|
|
20
|
+
*
|
|
21
|
+
* @param context - The rendering context containing the types reader
|
|
22
|
+
* @param propType - The API node representing the prop's type
|
|
23
|
+
* @returns An HTML string representation of the type (wrapped in code tags)
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* renderPropTypeToHtml(context, stringNode) // Returns: "<code>string</code>"
|
|
28
|
+
* renderPropTypeToHtml(context, unionNode) // Returns: "<code>boolean</code> | <code>number</code> | <code>string</code>"
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare const renderPropTypeToHtml: (context: RenderTypeContext, propType: ApiNode) => string;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { isBuiltInTypeReferenceNode, isExternalTypeReferenceNode, isFunctionNode, isInlinedTypeReferenceNode, isLiteralNode, isObjectNode, isPrimitiveNode, isTypeReferenceNode, isUnionNode, } from '@hubspot/ts-export-types-reader';
|
|
2
|
+
import { wrapCodeInHtmlCodeTags } from "./utils/html-utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Renders an object type node as a string representation.
|
|
5
|
+
*
|
|
6
|
+
* Converts an ObjectNode into a TypeScript-like object type string, e.g.:
|
|
7
|
+
* `{ name: string, age: number }`
|
|
8
|
+
*
|
|
9
|
+
* @param context - The rendering context containing the types reader
|
|
10
|
+
* @param objectTypeNode - The object type node to render
|
|
11
|
+
* @returns A string representation of the object type
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* renderObjectType(context, objectNode)
|
|
16
|
+
* // Returns: "{ name: string, age: number }"
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
const renderObjectType = (context, objectTypeNode) => {
|
|
20
|
+
const { properties } = objectTypeNode;
|
|
21
|
+
// Empty object types render as just {}
|
|
22
|
+
if (properties.length === 0) {
|
|
23
|
+
return `{}`;
|
|
24
|
+
}
|
|
25
|
+
// Map each property to "name: type" format and join with commas
|
|
26
|
+
const propertyParts = properties.map((property) => {
|
|
27
|
+
return `${property.name}: ${renderType(context, property.type)}`;
|
|
28
|
+
});
|
|
29
|
+
return `{ ${propertyParts.join(', ')} }`;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Renders a function type node as a string representation.
|
|
33
|
+
*
|
|
34
|
+
* Converts a FunctionNode into a TypeScript-like function type string, e.g.:
|
|
35
|
+
* `(param1: string, param2: number) => boolean`
|
|
36
|
+
*
|
|
37
|
+
* @param context - The rendering context containing the types reader
|
|
38
|
+
* @param functionNode - The function type node to render
|
|
39
|
+
* @returns A string representation of the function type
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* renderFunction(context, functionNode)
|
|
44
|
+
* // Returns: "(value: string) => boolean"
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
const renderFunction = (context, functionNode) => {
|
|
48
|
+
const paramParts = [];
|
|
49
|
+
// Render each parameter as "name: type"
|
|
50
|
+
for (const param of functionNode.parameters) {
|
|
51
|
+
paramParts.push(`${param.name}: ${renderType(context, param.type)}`);
|
|
52
|
+
}
|
|
53
|
+
// Combine parameters and return type into function signature format
|
|
54
|
+
return `(${paramParts.join(', ')}) => ${renderType(context, functionNode.returnType)}`;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Main type rendering function that converts an API node to a string representation.
|
|
58
|
+
*
|
|
59
|
+
* This is a recursive function that handles all TypeScript type constructs:
|
|
60
|
+
* - Primitives (string, number, boolean, etc.)
|
|
61
|
+
* - Literal types (e.g., "hello" | "world")
|
|
62
|
+
* - External type references (types from other packages)
|
|
63
|
+
* - Function types
|
|
64
|
+
* - Object types
|
|
65
|
+
* - Inlined type references
|
|
66
|
+
* - Built-in generic types (Array<T>, Promise<T>, etc.)
|
|
67
|
+
* - Type references (aliases and interfaces)
|
|
68
|
+
* - Union types
|
|
69
|
+
*
|
|
70
|
+
* The function uses the context to track already-seen type IDs to prevent
|
|
71
|
+
* infinite recursion when rendering circular type references.
|
|
72
|
+
*
|
|
73
|
+
* @param context - The rendering context containing the types reader and found type IDs
|
|
74
|
+
* @param typeNode - The API node representing the type to render
|
|
75
|
+
* @returns A string representation of the type (e.g., "string", "Array<number>", "string | number")
|
|
76
|
+
*/
|
|
77
|
+
const renderType = (context, typeNode) => {
|
|
78
|
+
// Handle primitive types (string, number, boolean, etc.)
|
|
79
|
+
if (isPrimitiveNode(typeNode)) {
|
|
80
|
+
return typeNode.name;
|
|
81
|
+
}
|
|
82
|
+
// Handle literal types (e.g., "hello", 42, true)
|
|
83
|
+
if (isLiteralNode(typeNode)) {
|
|
84
|
+
return JSON.stringify(typeNode.value);
|
|
85
|
+
}
|
|
86
|
+
// Handle external type references (types from other packages/modules)
|
|
87
|
+
// These are rendered as-is since we don't have their definitions
|
|
88
|
+
if (isExternalTypeReferenceNode(typeNode)) {
|
|
89
|
+
return typeNode.typeString;
|
|
90
|
+
}
|
|
91
|
+
// Handle function types
|
|
92
|
+
if (isFunctionNode(typeNode)) {
|
|
93
|
+
return renderFunction(context, typeNode);
|
|
94
|
+
}
|
|
95
|
+
// Handle object types
|
|
96
|
+
if (isObjectNode(typeNode)) {
|
|
97
|
+
return renderObjectType(context, typeNode);
|
|
98
|
+
}
|
|
99
|
+
// Handle inlined type references - recursively render the inner type
|
|
100
|
+
if (isInlinedTypeReferenceNode(typeNode)) {
|
|
101
|
+
return renderType(context, typeNode.type);
|
|
102
|
+
}
|
|
103
|
+
// Handle built-in generic types (Array<T>, Promise<T>, etc.)
|
|
104
|
+
if (isBuiltInTypeReferenceNode(typeNode)) {
|
|
105
|
+
const { name, typeArguments } = typeNode;
|
|
106
|
+
// Render type arguments if present (e.g., <string, number>)
|
|
107
|
+
const renderedTypeArguments = typeArguments
|
|
108
|
+
? `<${typeArguments
|
|
109
|
+
.map((argType) => renderType(context, argType))
|
|
110
|
+
.join(', ')}>`
|
|
111
|
+
: '';
|
|
112
|
+
return `${name}${renderedTypeArguments}`;
|
|
113
|
+
}
|
|
114
|
+
// Handle type references (aliases, interfaces, etc.)
|
|
115
|
+
if (isTypeReferenceNode(typeNode)) {
|
|
116
|
+
// Check if we've already seen this type ID to prevent infinite recursion
|
|
117
|
+
// If we have, return the type string representation instead of expanding
|
|
118
|
+
if (context.foundTypeIds.has(typeNode.typeId)) {
|
|
119
|
+
return typeNode.typeString;
|
|
120
|
+
}
|
|
121
|
+
// Mark this type as found before recursing
|
|
122
|
+
context.foundTypeIds.add(typeNode.typeId);
|
|
123
|
+
// Look up the referenced type definition and render it
|
|
124
|
+
const referencedNode = context.typesReader.findReferencedTypeById(typeNode.typeId);
|
|
125
|
+
return renderType(context, referencedNode);
|
|
126
|
+
}
|
|
127
|
+
// Handle union types (e.g., string | number | boolean)
|
|
128
|
+
if (isUnionNode(typeNode)) {
|
|
129
|
+
// Render each type in the union
|
|
130
|
+
const renderedUnionParts = typeNode.types.map((unionType) => renderType(context, unionType));
|
|
131
|
+
// Sort union parts alphabetically for consistent output
|
|
132
|
+
renderedUnionParts.sort((a, b) => a.localeCompare(b));
|
|
133
|
+
return renderedUnionParts.join(' \\| ');
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Unsupported type node: ${typeNode.kind}`);
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Checks if a value is a non-empty array.
|
|
139
|
+
*
|
|
140
|
+
* @param array - The value to check
|
|
141
|
+
* @returns True if the value is an array with at least one element
|
|
142
|
+
*/
|
|
143
|
+
const isNonEmptyArray = (array) => Array.isArray(array) && array.length > 0;
|
|
144
|
+
/**
|
|
145
|
+
* Checks if a type node has type arguments (generic parameters).
|
|
146
|
+
*
|
|
147
|
+
* @param typeNode - The type node to check (must be InlinedTypeReferenceNode or TypeReferenceNode)
|
|
148
|
+
* @returns True if the type node has type arguments
|
|
149
|
+
*/
|
|
150
|
+
const hasTypeArgs = (typeNode) => isNonEmptyArray(typeNode.typeArguments);
|
|
151
|
+
/**
|
|
152
|
+
* Resolves a type node to its target type, handling type references and inlined references.
|
|
153
|
+
*
|
|
154
|
+
* For prop type rendering, we want to resolve type references to their actual definitions,
|
|
155
|
+
* but we want to preserve generic type arguments. This function:
|
|
156
|
+
* - For inlined type references: returns the node itself if it has type args, otherwise resolves to inner type
|
|
157
|
+
* - For type references: returns the node itself if it has type args, otherwise resolves to referenced type
|
|
158
|
+
* - For other nodes: returns the node as-is
|
|
159
|
+
*
|
|
160
|
+
* This ensures that generic types like `Array<string>` are preserved, while simple
|
|
161
|
+
* type aliases are resolved to their underlying types.
|
|
162
|
+
*
|
|
163
|
+
* @param context - The rendering context containing the types reader
|
|
164
|
+
* @param typeNode - The type node to resolve
|
|
165
|
+
* @returns The resolved target type node
|
|
166
|
+
*/
|
|
167
|
+
const getTargetTypeNode = (context, typeNode) => {
|
|
168
|
+
// For inlined type references, preserve the node if it has type args (generics)
|
|
169
|
+
// Otherwise, resolve to the inner type
|
|
170
|
+
if (isInlinedTypeReferenceNode(typeNode)) {
|
|
171
|
+
return hasTypeArgs(typeNode) ? typeNode : typeNode.type;
|
|
172
|
+
}
|
|
173
|
+
// For type references, preserve the node if it has type args (generics)
|
|
174
|
+
// Otherwise, look up and return the referenced type definition
|
|
175
|
+
if (isTypeReferenceNode(typeNode)) {
|
|
176
|
+
return hasTypeArgs(typeNode)
|
|
177
|
+
? typeNode
|
|
178
|
+
: context.typesReader.findReferencedTypeById(typeNode.typeId);
|
|
179
|
+
}
|
|
180
|
+
// For other node types, return as-is
|
|
181
|
+
return typeNode;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Renders a prop's type as an HTML string.
|
|
185
|
+
*
|
|
186
|
+
* This function handles the special case of union types in props, which need
|
|
187
|
+
* to be rendered recursively. All types are wrapped in HTML code tags for
|
|
188
|
+
* proper HTML rendering.
|
|
189
|
+
*
|
|
190
|
+
* @param context - The rendering context containing the types reader
|
|
191
|
+
* @param propType - The API node representing the prop's type
|
|
192
|
+
* @returns An HTML string representation of the type (wrapped in code tags)
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* renderPropTypeToHtml(context, stringNode) // Returns: "<code>string</code>"
|
|
197
|
+
* renderPropTypeToHtml(context, unionNode) // Returns: "<code>boolean</code> | <code>number</code> | <code>string</code>"
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
export const renderPropTypeToHtml = (context, propType) => {
|
|
201
|
+
// Resolve references to other types to the actual type node
|
|
202
|
+
// This handles type aliases and ensures generics are preserved
|
|
203
|
+
const targetPropType = getTargetTypeNode(context, propType);
|
|
204
|
+
// Special handling for union types: render each member recursively and sort
|
|
205
|
+
// This ensures nested unions are properly formatted
|
|
206
|
+
if (isUnionNode(targetPropType)) {
|
|
207
|
+
const renderedUnionTypes = targetPropType.types.map((currentType) => renderPropTypeToHtml(context, currentType));
|
|
208
|
+
// Sort union parts alphabetically for consistent output
|
|
209
|
+
renderedUnionTypes.sort((a, b) => a.localeCompare(b));
|
|
210
|
+
return renderedUnionTypes.join(' | ');
|
|
211
|
+
}
|
|
212
|
+
return wrapCodeInHtmlCodeTags(renderType(context, targetPropType));
|
|
213
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps code as text in HTML code tags for HTML rendering. Special HTML characters are escaped.
|
|
3
|
+
*
|
|
4
|
+
* @param value - The code as text to wrap in HTML code tags
|
|
5
|
+
* @returns The code as text wrapped in HTML code tags
|
|
6
|
+
*/
|
|
7
|
+
export declare const wrapCodeInHtmlCodeTags: (value: string) => string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const sanitizeHtmlText = (htmlText) => {
|
|
2
|
+
return (htmlText
|
|
3
|
+
.replaceAll('&', '&')
|
|
4
|
+
.replaceAll('<', '<')
|
|
5
|
+
.replaceAll('>', '>')
|
|
6
|
+
.replaceAll('"', '"')
|
|
7
|
+
// Use double quotes for consistency with other entity references
|
|
8
|
+
.replaceAll("'", '''));
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Wraps code as text in HTML code tags for HTML rendering. Special HTML characters are escaped.
|
|
12
|
+
*
|
|
13
|
+
* @param value - The code as text to wrap in HTML code tags
|
|
14
|
+
* @returns The code as text wrapped in HTML code tags
|
|
15
|
+
*/
|
|
16
|
+
export const wrapCodeInHtmlCodeTags = (value) => {
|
|
17
|
+
return `<code>${sanitizeHtmlText(value)}</code>`;
|
|
18
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSDoc utilities for extracting metadata from JSDoc tags.
|
|
3
|
+
*
|
|
4
|
+
* This module provides helper functions for parsing and extracting information
|
|
5
|
+
* from JSDoc comments associated with component props, such as default values.
|
|
6
|
+
*/
|
|
7
|
+
import { JsdocTag } from '@hubspot/ts-export-types-reader';
|
|
8
|
+
/**
|
|
9
|
+
* Extracts the default value from JSDoc tags.
|
|
10
|
+
*
|
|
11
|
+
* Searches through an array of JSDoc tags to find a tag named 'defaultValue'
|
|
12
|
+
* and returns its text content. This is used to display default values in
|
|
13
|
+
* the component props documentation tables.
|
|
14
|
+
*
|
|
15
|
+
* @param tags - An array of JSDoc tags, or undefined if no tags exist
|
|
16
|
+
* @returns The default value text if found, or null if not found or if tags is undefined
|
|
17
|
+
*/
|
|
18
|
+
export declare const getDefaultValueFromJsdocTags: (tags: JsdocTag[] | undefined) => string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Extracts see tags from JSDoc tags.
|
|
21
|
+
*
|
|
22
|
+
* Searches through an array of JSDoc tags to find tags named 'see'
|
|
23
|
+
* and returns their text content. See tags are used to reference related
|
|
24
|
+
* documentation or external resources.
|
|
25
|
+
*
|
|
26
|
+
* @param tags - An array of JSDoc tags, or undefined if no tags exist
|
|
27
|
+
* @returns An array of see tag text values, or an empty array if none found
|
|
28
|
+
*/
|
|
29
|
+
export declare const getSeeTagsFromJsdocTags: (tags: JsdocTag[] | undefined) => string[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSDoc utilities for extracting metadata from JSDoc tags.
|
|
3
|
+
*
|
|
4
|
+
* This module provides helper functions for parsing and extracting information
|
|
5
|
+
* from JSDoc comments associated with component props, such as default values.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Extracts the default value from JSDoc tags.
|
|
9
|
+
*
|
|
10
|
+
* Searches through an array of JSDoc tags to find a tag named 'defaultValue'
|
|
11
|
+
* and returns its text content. This is used to display default values in
|
|
12
|
+
* the component props documentation tables.
|
|
13
|
+
*
|
|
14
|
+
* @param tags - An array of JSDoc tags, or undefined if no tags exist
|
|
15
|
+
* @returns The default value text if found, or null if not found or if tags is undefined
|
|
16
|
+
*/
|
|
17
|
+
export const getDefaultValueFromJsdocTags = (tags) => {
|
|
18
|
+
// Return null if there are no tags
|
|
19
|
+
if (!tags) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
// Search through tags to find the defaultValue tag
|
|
23
|
+
for (const tag of tags) {
|
|
24
|
+
if (tag.name === 'defaultValue') {
|
|
25
|
+
// Return the tag's text, or null if text is empty/undefined
|
|
26
|
+
return tag.text || null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// No defaultValue tag found
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Extracts see tags from JSDoc tags.
|
|
34
|
+
*
|
|
35
|
+
* Searches through an array of JSDoc tags to find tags named 'see'
|
|
36
|
+
* and returns their text content. See tags are used to reference related
|
|
37
|
+
* documentation or external resources.
|
|
38
|
+
*
|
|
39
|
+
* @param tags - An array of JSDoc tags, or undefined if no tags exist
|
|
40
|
+
* @returns An array of see tag text values, or an empty array if none found
|
|
41
|
+
*/
|
|
42
|
+
export const getSeeTagsFromJsdocTags = (tags) => {
|
|
43
|
+
// Return empty array if there are no tags
|
|
44
|
+
if (!tags) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
// Search through tags to find the @see tags and extract their text
|
|
48
|
+
// Filter out any tags with empty or undefined text
|
|
49
|
+
return tags
|
|
50
|
+
.filter((tag) => tag.name === 'see' && tag.text)
|
|
51
|
+
.map((tag) => tag.text);
|
|
52
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Marked } from 'marked';
|
|
2
|
+
const markedInstance = new Marked({
|
|
3
|
+
breaks: true,
|
|
4
|
+
gfm: true,
|
|
5
|
+
});
|
|
6
|
+
/**
|
|
7
|
+
* Renders inline markdown text to HTML.
|
|
8
|
+
*
|
|
9
|
+
* @param markdownText - The markdown text to render to HTML
|
|
10
|
+
* @returns The markdown text rendered to HTML
|
|
11
|
+
*/
|
|
12
|
+
export const renderInlineMarkdownToHtml = (markdownText) => {
|
|
13
|
+
return markedInstance.parseInline(markdownText).toString().trim();
|
|
14
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation for a component prop.
|
|
3
|
+
*/
|
|
4
|
+
export interface ComponentPropDocumentation {
|
|
5
|
+
/**
|
|
6
|
+
* The name of the prop.
|
|
7
|
+
*/
|
|
8
|
+
name: string;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the prop is required.
|
|
11
|
+
*/
|
|
12
|
+
required: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The type of the prop as a JSX code string (e.g., `"<><code>boolean</code></>"`).
|
|
15
|
+
*/
|
|
16
|
+
typeJsx: string;
|
|
17
|
+
/**
|
|
18
|
+
* The default value of the prop as a JSX code string (e.g., `"<><code>false</code></>"`).
|
|
19
|
+
*/
|
|
20
|
+
defaultValueJsx?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The description of the prop as a JSX code string (e.g., `"<>The description of the prop.</>"`).
|
|
23
|
+
*/
|
|
24
|
+
descriptionJsx: string;
|
|
25
|
+
/**
|
|
26
|
+
* The see items of the prop as JSX code strings (e.g., `["<><a href="https://example.com">Example</a></>"]`).
|
|
27
|
+
*
|
|
28
|
+
* These are typically markup for links to other documentation related to the prop.
|
|
29
|
+
*/
|
|
30
|
+
seeJsxItems?: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The names of the components in the `@hubspot/ui-extensions` package.
|
|
34
|
+
*
|
|
35
|
+
* FUTURE: We should use code generation to generate this enum.
|
|
36
|
+
*/
|
|
37
|
+
export declare enum ComponentName {
|
|
38
|
+
Accordion = "Accordion"
|
|
39
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The names of the components in the `@hubspot/ui-extensions` package.
|
|
3
|
+
*
|
|
4
|
+
* FUTURE: We should use code generation to generate this enum.
|
|
5
|
+
*/
|
|
6
|
+
export var ComponentName;
|
|
7
|
+
(function (ComponentName) {
|
|
8
|
+
ComponentName["Accordion"] = "Accordion";
|
|
9
|
+
})(ComponentName || (ComponentName = {}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions-sdk-api-metadata",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "UI Extensions SDK API Metadata",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"check:tsc": "tsc",
|
|
10
10
|
"generate": "npm run generate:sdk-api-metadata",
|
|
11
11
|
"generate:sdk-api-metadata": "(cd ../ui-extensions-components && npm run build) && ts-export-types analyze --config ts-export-types.component-props.config.js",
|
|
12
|
-
"test": "
|
|
12
|
+
"test": "vitest run",
|
|
13
13
|
"watch": "npm run clean && tsc --watch",
|
|
14
14
|
"prepublishOnly": "npm run build",
|
|
15
15
|
"lint": "echo 'No linter configured for @hubspot/ui-extensions-sdk-api-metadata'"
|
|
@@ -21,18 +21,21 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"exports": {
|
|
24
|
-
"
|
|
24
|
+
".": "./dist/index.js"
|
|
25
25
|
},
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@hubspot/ts-export-types-reader": "0.1.2"
|
|
28
|
+
"@hubspot/ts-export-types-reader": "0.1.2",
|
|
29
|
+
"marked": "17.0.1"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@hubspot/ts-export-types": "0.1.2",
|
|
32
|
-
"
|
|
33
|
+
"@vitest/coverage-v8": "2.1.8",
|
|
34
|
+
"typescript": "5.9.3",
|
|
35
|
+
"vitest": "2.1.9"
|
|
33
36
|
},
|
|
34
37
|
"engines": {
|
|
35
38
|
"node": ">=16"
|
|
36
39
|
},
|
|
37
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "c5b66da065a341df3afd98ce6641bb3e6317aeb4"
|
|
38
41
|
}
|