Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs-infra] Add support for data attributes in the API generation #44709

Merged
merged 8 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/pages/material-ui/api/accordion.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@
"class": null
}
],
"dataAttributes": {
"[data-nested-dialogs]": {
"description": "Indicates how many dialogs are nested within.",
"type": "number"
},
"[data-panel-open]": { "description": "Indicates whether the panel is opened" }
},
"classes": [
{
"key": "disabled",
Expand Down
4 changes: 4 additions & 0 deletions docs/translations/api-docs/accordion/accordion.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,9 @@
"slotDescriptions": {
"heading": "The component that renders the heading.",
"transition": "The component that renders the transition. <a href=\"https://mui.com/material-ui/transitions/#transitioncomponent-prop\">Follow this guide</a> to learn more about the requirements for this component."
},
"dataAttributesDescriptions": {
"[data-nested-dialogs]": "Indicates how many dialogs are nested within.",
"[data-panel-open]": "Indicates whether the panel is opened"
}
}
57 changes: 45 additions & 12 deletions packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
ComponentReactApi,
ParsedProperty,
} from '../types/ApiBuilder.types';
import { Slot, ComponentInfo } from '../types/utils.types';
import { Slot, ComponentInfo, ApiItemDescription } from '../types/utils.types';
import extractInfoFromEnum from '../utils/extractInfoFromEnum';

const cssComponents = ['Box', 'Grid', 'Typography', 'Stack'];
Expand Down Expand Up @@ -335,6 +335,9 @@ const generateApiPage = async (
imports: reactApi.imports,
...(reactApi.slots?.length > 0 && { slots: reactApi.slots }),
...(Object.keys(reactApi.cssVariables).length > 0 && { cssVariables: reactApi.cssVariables }),
...(Object.keys(reactApi.dataAttributes).length > 0 && {
dataAttributes: reactApi.dataAttributes,
}),
classes: reactApi.classes,
spread: reactApi.spread,
themeDefaultProps: reactApi.themeDefaultProps,
Expand Down Expand Up @@ -488,6 +491,20 @@ const attachTranslations = (
});
}

/**
* Data attributes descriptions.
*/
if (Object.keys(reactApi.dataAttributes).length > 0) {
translations.dataAttributesDescriptions = {};
[...Object.keys(reactApi.dataAttributes)]
.sort() // Sort to ensure consistency of object key order
.forEach((dataAttributeName: string) => {
const dataAttribute = reactApi.dataAttributes[dataAttributeName];
const { description } = dataAttribute;
translations.dataAttributesDescriptions![dataAttributeName] = renderMarkdown(description);
});
}

reactApi.translations = translations;
};

Expand Down Expand Up @@ -635,16 +652,21 @@ const defaultGetComponentImports = (name: string, filename: string) => {
return [subpathImport, rootImport];
};

const attachCssVariables = (reactApi: ComponentReactApi, params: ParsedProperty[]) => {
const cssVarsErrors: Array<[propName: string, error: Error]> = [];
const cssVariables: ComponentReactApi['cssVariables'] = params
const attachTable = (
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repurposed the function to be used with any attribute

reactApi: ComponentReactApi,
params: ParsedProperty[],
attribute: 'cssVariables' | 'dataAttributes',
defaultType: any = undefined,
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
) => {
const errors: Array<[propName: string, error: Error]> = [];
const data: { [key: string]: ApiItemDescription } = params
.map((p) => {
const { name: propName, ...propDescriptor } = p;
let prop: Omit<ParsedProperty, 'name'> | null;
try {
prop = propDescriptor;
} catch (error) {
cssVarsErrors.push([propName, error as Error]);
errors.push([propName, error as Error]);
prop = null;
}
if (prop === null) {
Expand All @@ -656,7 +678,11 @@ const attachCssVariables = (reactApi: ComponentReactApi, params: ParsedProperty[
const deprecation = deprecationTag?.text?.[0]?.text;

const typeTag = propDescriptor.tags?.type;
const type = (typeTag?.text?.[0]?.text ?? 'string').replace(/{|}/g, '');

let type = typeTag?.text?.[0]?.text ?? defaultType;
if (typeof type === 'string') {
type = type.replace(/{|}/g, '');
}
mnajdova marked this conversation as resolved.
Show resolved Hide resolved

return {
name: propName,
Expand All @@ -674,17 +700,17 @@ const attachCssVariables = (reactApi: ComponentReactApi, params: ParsedProperty[
};
}, {});

if (cssVarsErrors.length > 0) {
if (errors.length > 0) {
throw new Error(
`There were errors creating CSS variable descriptions:\n${cssVarsErrors
.map(([cssVarName, error]) => {
return ` - ${cssVarName}: ${error}`;
`There were errors creating ${attribute.replace(/([A-Z])/g, ' $1')} descriptions:\n${errors
.map(([item, error]) => {
return ` - ${item}: ${error}`;
})
.join('\n')}`,
);
}

reactApi.cssVariables = cssVariables;
reactApi[attribute] = data;
};

/**
Expand Down Expand Up @@ -844,8 +870,15 @@ export default async function generateComponentApi(
project,
);

const dataAttributes = await extractInfoFromEnum(
`${componentInfo.name}DataAttributes`,
new RegExp(`${componentInfo.name}(DataAttributes)?.tsx?$`, 'i'),
project,
);

attachPropsTable(reactApi, projectSettings.propsSettings);
attachCssVariables(reactApi, cssVars);
attachTable(reactApi, cssVars, 'cssVariables', 'string');
attachTable(reactApi, dataAttributes, 'dataAttributes');
attachTranslations(reactApi, deprecationInfo, projectSettings.propsSettings);

// eslint-disable-next-line no-console
Expand Down
9 changes: 6 additions & 3 deletions packages/api-docs-builder/types/ApiBuilder.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactDocgenApi } from 'react-docgen';
import { JSDocTagInfo } from 'typescript';
import { ComponentInfo, Slot, HookInfo, SeeMore, CssVariable } from './utils.types';
import { ComponentInfo, Slot, HookInfo, SeeMore, ApiItemDescription } from './utils.types';

export type AdditionalPropsInfo = {
cssApi?: boolean;
Expand Down Expand Up @@ -58,6 +58,7 @@ export interface PropsTranslations {
};
slotDescriptions?: { [key: string]: string };
cssVariablesDescriptions?: { [key: string]: string };
dataAttributesDescriptions?: { [key: string]: string };
}

interface PropDescription {
Expand Down Expand Up @@ -93,7 +94,8 @@ export interface ComponentReactApi extends CommonReactApi {
themeDefaultProps: boolean | undefined | null;
classes: ComponentClassDefinition[];
slots: Slot[];
cssVariables: { [key: string]: CssVariable };
cssVariables: { [key: string]: ApiItemDescription };
dataAttributes: { [key: string]: ApiItemDescription };
propsTable: _.Dictionary<PropsTableItem>;
translations: PropsTranslations;
}
Expand All @@ -103,7 +105,8 @@ export interface ComponentApiContent {
name: string;
imports: string[];
slots?: Slot[];
cssVariables?: { [key: string]: CssVariable };
cssVariables?: { [key: string]: ApiItemDescription };
dataAttributes?: { [key: string]: ApiItemDescription };
classes: ComponentClassDefinition[];
spread: boolean | undefined;
themeDefaultProps: boolean | null | undefined;
Expand Down
2 changes: 1 addition & 1 deletion packages/api-docs-builder/types/utils.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface Slot {
default?: string;
}

export interface CssVariable {
export interface ApiItemDescription {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the name more generic to be reused with different types of API descriptions.

name: string;
description: string;
}
Expand Down
5 changes: 2 additions & 3 deletions packages/api-docs-builder/utils/extractInfoFromEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ const extractInfoFromEnum = async (
): Promise<ParsedProperty[]> => {
// Generate the params
let result: ParsedProperty[] = [];

try {
const cssVarDeclarationCandidates = project.program
const declarationCandidates = project.program
.getSourceFiles()
.filter((file) => sourceFileNamePattern.test(file.fileName));

let enumSymbol: Symbol | null = null;
cssVarDeclarationCandidates.forEach((file) => {
declarationCandidates.forEach((file) => {
forEachChild(file, (node: Node) => {
if (isEnumDeclaration(node) && node.name.getText() === typeName) {
enumSymbol = project.checker.getSymbolAtLocation(node.name)!;
Expand Down
13 changes: 13 additions & 0 deletions packages/mui-material/src/Accordion/AccordionDataAttributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
enum AccordionDataAttributes {
/**
* Indicates whether the panel is opened
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Indicates whether the panel is opened
* Indicates whether the panel is opened.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note, I was thinking of making a quick PR to fail the CI anytime any of those descriptions doesn't end with a dot. I suspect it's a quick win.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am reverting these changes, they are just here to help the reviewer :)

*/
panelOpen = '[data-panel-open]',
/**
* Indicates how many dialogs are nested within.
* @type {number}
*/
nestedDialogs = '[data-nested-dialogs]',
}

export default AccordionDataAttributes;
Loading