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

[code-infra] Migrate to prettier async APIs #40668

Merged
merged 14 commits into from
Jan 24, 2024
8 changes: 4 additions & 4 deletions docs/scripts/formattedTSDemos.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,19 @@ async function transpileFile(tsxPath, project) {
},
});
const codeWithPropTypes = injectPropTypesInFile({ components: propTypesAST, target: code });
const prettierConfig = prettier.resolveConfig.sync(jsPath, {
const prettierConfig = await prettier.resolveConfig(jsPath, {
config: path.join(workspaceRoot, 'prettier.config.js'),
});
const prettierFormat = (jsSource) =>
const prettierFormat = async (jsSource) =>
prettier.format(jsSource, { ...prettierConfig, filepath: jsPath });

const codeWithoutTsIgnoreComments = codeWithPropTypes.replace(/^\s*\/\/ @ts-ignore.*$/gm, '');
const prettified = prettierFormat(codeWithoutTsIgnoreComments);
const prettified = await prettierFormat(codeWithoutTsIgnoreComments);
const formatted = fixBabelGeneratorIssues(prettified);
const correctedLineEndings = fixLineEndings(source, formatted);

// removed blank lines change potential formatting
await fse.writeFile(jsPath, prettierFormat(correctedLineEndings));
await fse.writeFile(jsPath, await prettierFormat(correctedLineEndings));
return TranspileResult.Success;
} catch (err) {
console.error('Something went wrong transpiling %s\n%s\n', tsxPath, err);
Expand Down
162 changes: 82 additions & 80 deletions packages/api-docs-builder-core/baseUi/generateBaseUiApiPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@ import { getHeaders } from '@mui/markdown';
import findPagesMarkdown from '@mui-internal/api-docs-builder/utils/findPagesMarkdown';
import { writePrettifiedFile } from '@mui-internal/api-docs-builder/buildApiUtils';

export function generateBaseUIApiPages() {
findPagesMarkdown().forEach((markdown) => {
const markdownContent = fs.readFileSync(markdown.filename, 'utf8');
const markdownHeaders = getHeaders(markdownContent) as any;
const pathnameTokens = markdown.pathname.split('/');
const productName = pathnameTokens[1];
const componentName = pathnameTokens[3];

// TODO: fix `productName` should be called `productId` and include the full name,
// e.g. base-ui below.
if (
productName === 'base' &&
(markdown.filename.indexOf('\\components\\') >= 0 ||
markdown.filename.indexOf('/components/') >= 0)
) {
const { components, hooks } = markdownHeaders;

const tokens = markdown.pathname.split('/');
const name = tokens[tokens.length - 1];
const importStatement = `docs/data${markdown.pathname}/${name}.md`;
const demosSource = `
export async function generateBaseUIApiPages() {
await Promise.all(
findPagesMarkdown().map(async (markdown) => {
const markdownContent = fs.readFileSync(markdown.filename, 'utf8');
const markdownHeaders = getHeaders(markdownContent) as any;
const pathnameTokens = markdown.pathname.split('/');
const productName = pathnameTokens[1];
const componentName = pathnameTokens[3];

// TODO: fix `productName` should be called `productId` and include the full name,
// e.g. base-ui below.
if (
productName === 'base' &&
(markdown.filename.indexOf('\\components\\') >= 0 ||
markdown.filename.indexOf('/components/') >= 0)
) {
const { components, hooks } = markdownHeaders;

const tokens = markdown.pathname.split('/');
const name = tokens[tokens.length - 1];
const importStatement = `docs/data${markdown.pathname}/${name}.md`;
const demosSource = `
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocsV2';
import AppFrame from 'docs/src/modules/components/AppFrame';
Expand All @@ -41,77 +42,77 @@ Page.getLayout = (page) => {
};
`;

const componentPageDirectory = `docs/pages/${productName}-ui/react-${componentName}/`;
if (!fs.existsSync(componentPageDirectory)) {
fs.mkdirSync(componentPageDirectory, { recursive: true });
}
writePrettifiedFile(
path.join(process.cwd(), `${componentPageDirectory}/index.js`),
demosSource,
);

if ((!components || components.length === 0) && (!hooks || hooks.length === 0)) {
// Early return if it's a markdown file without components/hooks.
return;
}

let apiTabImportStatements = '';
let staticProps = 'export const getStaticProps = () => {';
let componentsApiDescriptions = '';
let componentsPageContents = '';
let hooksApiDescriptions = '';
let hooksPageContents = '';

if (components && components.length > 0) {
components.forEach((component: string) => {
const componentNameKebabCase = kebabCase(component);
apiTabImportStatements += `import ${component}ApiJsonPageContent from '../../api/${componentNameKebabCase}.json';`;
staticProps += `
const componentPageDirectory = `docs/pages/${productName}-ui/react-${componentName}/`;
if (!fs.existsSync(componentPageDirectory)) {
fs.mkdirSync(componentPageDirectory, { recursive: true });
}
await writePrettifiedFile(
path.join(process.cwd(), `${componentPageDirectory}/index.js`),
demosSource,
);

if ((!components || components.length === 0) && (!hooks || hooks.length === 0)) {
// Early return if it's a markdown file without components/hooks.
return;
}

let apiTabImportStatements = '';
let staticProps = 'export const getStaticProps = () => {';
let componentsApiDescriptions = '';
let componentsPageContents = '';
let hooksApiDescriptions = '';
let hooksPageContents = '';

if (components && components.length > 0) {
components.forEach((component: string) => {
const componentNameKebabCase = kebabCase(component);
apiTabImportStatements += `import ${component}ApiJsonPageContent from '../../api/${componentNameKebabCase}.json';`;
staticProps += `
const ${component}ApiReq = require.context(
'docs/translations/api-docs-base/${componentNameKebabCase}',
false,
/${componentNameKebabCase}.*.json$/,
);
const ${component}ApiDescriptions = mapApiPageTranslations(${component}ApiReq);
`;
componentsApiDescriptions += `${component} : ${component}ApiDescriptions ,`;
componentsPageContents += `${component} : ${component}ApiJsonPageContent ,`;
});
}

if (hooks && hooks.length > 0) {
hooks.forEach((hook: string) => {
const hookNameKebabCase = kebabCase(hook);
apiTabImportStatements += `import ${hook}ApiJsonPageContent from '../../api/${hookNameKebabCase}.json';`;
staticProps += `
componentsApiDescriptions += `${component} : ${component}ApiDescriptions ,`;
componentsPageContents += `${component} : ${component}ApiJsonPageContent ,`;
});
}

if (hooks && hooks.length > 0) {
hooks.forEach((hook: string) => {
const hookNameKebabCase = kebabCase(hook);
apiTabImportStatements += `import ${hook}ApiJsonPageContent from '../../api/${hookNameKebabCase}.json';`;
staticProps += `
const ${hook}ApiReq = require.context(
'docs/translations/api-docs/${hookNameKebabCase}',
false,
/${hookNameKebabCase}.*.json$/,
);
const ${hook}ApiDescriptions = mapApiPageTranslations(${hook}ApiReq);
`;
hooksApiDescriptions += `${hook} : ${hook}ApiDescriptions ,`;
hooksPageContents += `${hook} : ${hook}ApiJsonPageContent ,`;
});
}
hooksApiDescriptions += `${hook} : ${hook}ApiDescriptions ,`;
hooksPageContents += `${hook} : ${hook}ApiJsonPageContent ,`;
});
}

staticProps += `
staticProps += `
return { props: { componentsApiDescriptions: {`;
staticProps += componentsApiDescriptions;
staticProps += componentsApiDescriptions;

staticProps += '}, componentsApiPageContents: { ';
staticProps += componentsPageContents;
staticProps += '}, componentsApiPageContents: { ';
staticProps += componentsPageContents;

staticProps += '}, hooksApiDescriptions: {';
staticProps += hooksApiDescriptions;
staticProps += '}, hooksApiDescriptions: {';
staticProps += hooksApiDescriptions;

staticProps += '}, hooksApiPageContents: {';
staticProps += hooksPageContents;
staticProps += '}, hooksApiPageContents: {';
staticProps += hooksPageContents;

staticProps += ` },},};};`;
staticProps += ` },},};};`;

const tabsApiSource = `
const tabsApiSource = `
import * as React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocsV2';
import AppFrame from 'docs/src/modules/components/AppFrame';
Expand All @@ -138,14 +139,15 @@ export const getStaticPaths = () => {
${staticProps}
`;

const docsTabsPagesDirectory = `${componentPageDirectory}/[docsTab]`;
if (!fs.existsSync(docsTabsPagesDirectory)) {
fs.mkdirSync(docsTabsPagesDirectory, { recursive: true });
const docsTabsPagesDirectory = `${componentPageDirectory}/[docsTab]`;
if (!fs.existsSync(docsTabsPagesDirectory)) {
fs.mkdirSync(docsTabsPagesDirectory, { recursive: true });
}
await writePrettifiedFile(
path.join(process.cwd(), `${docsTabsPagesDirectory}/index.js`),
tabsApiSource,
);
}
writePrettifiedFile(
path.join(process.cwd(), `${docsTabsPagesDirectory}/index.js`),
tabsApiSource,
);
}
});
}),
);
}
4 changes: 2 additions & 2 deletions packages/api-docs-builder-core/baseUi/projectSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export const projectSettings: ProjectSettings = {
getHookInfo: getBaseUiHookInfo,
translationLanguages: LANGUAGES,
skipComponent: () => false,
onCompleted: () => {
generateBaseUIApiPages();
onCompleted: async () => {
await generateBaseUIApiPages();
},
onWritingManifestFile(builds, source) {
const apiLinks = generateApiLinks(builds);
Expand Down
10 changes: 5 additions & 5 deletions packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ function extractClassCondition(description: string) {
return { description: renderMarkdown(description) };
}

const generateApiPage = (
const generateApiPage = async (
apiPagesDirectory: string,
importTranslationPagesDirectory: string,
reactApi: ReactApi,
Expand Down Expand Up @@ -412,13 +412,13 @@ const generateApiPage = (
pageContent.slots = [...pageContent.slots].sort(slotsSort);
}

writePrettifiedFile(
await writePrettifiedFile(
path.resolve(apiPagesDirectory, `${kebabCase(reactApi.name)}.json`),
JSON.stringify(pageContent),
);

if (!onlyJsonFile) {
writePrettifiedFile(
await writePrettifiedFile(
path.resolve(apiPagesDirectory, `${kebabCase(reactApi.name)}.js`),
`import * as React from 'react';
import ApiPage from 'docs/src/modules/components/ApiPage';
Expand Down Expand Up @@ -789,14 +789,14 @@ export default async function generateComponentApi(
generateJsonFileOnly,
} = projectSettings;

generateApiTranslations(
await generateApiTranslations(
path.join(process.cwd(), translationPagesDirectory),
reactApi,
projectSettings.translationLanguages,
);

// Once we have the tabs API in all projects, we can make this default
generateApiPage(
await generateApiPage(
componentInfo.apiPagesDirectory,
importTranslationPagesDirectory ?? translationPagesDirectory,
reactApi,
Expand Down
32 changes: 20 additions & 12 deletions packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ interface ParsedProperty {
typeStr: string;
}

const parseProperty = (propertySymbol: ts.Symbol, project: TypeScriptProject): ParsedProperty => ({
const parseProperty = async (
propertySymbol: ts.Symbol,
project: TypeScriptProject,
): Promise<ParsedProperty> => ({
name: propertySymbol.name,
description: getSymbolDescription(propertySymbol, project),
tags: getSymbolJSDocTags(propertySymbol),
required: !propertySymbol.declarations?.find(ts.isPropertySignature)?.questionToken,
typeStr: stringifySymbol(propertySymbol, project),
typeStr: await stringifySymbol(propertySymbol, project),
});

export interface ReactApi extends ReactDocgenApi {
Expand Down Expand Up @@ -394,7 +397,7 @@ const attachTranslations = (reactApi: ReactApi) => {
reactApi.translations = translations;
};

const generateApiJson = (outputDirectory: string, reactApi: ReactApi) => {
const generateApiJson = async (outputDirectory: string, reactApi: ReactApi) => {
/**
* Gather the metadata needed for the component's API page.
*/
Expand Down Expand Up @@ -430,13 +433,16 @@ const generateApiJson = (outputDirectory: string, reactApi: ReactApi) => {
.join('\n')}</ul>`,
};

writePrettifiedFile(
await writePrettifiedFile(
path.resolve(outputDirectory, `${kebabCase(reactApi.name)}.json`),
JSON.stringify(pageContent),
);
};

const extractInfoFromType = (typeName: string, project: TypeScriptProject): ParsedProperty[] => {
const extractInfoFromType = async (
typeName: string,
project: TypeScriptProject,
): Promise<ParsedProperty[]> => {
// Generate the params
let result: ParsedProperty[] = [];

Expand All @@ -454,9 +460,11 @@ const extractInfoFromType = (typeName: string, project: TypeScriptProject): Pars
const propertiesOnProject = type.getProperties();

// @ts-ignore
propertiesOnProject.forEach((propertySymbol) => {
properties[propertySymbol.name] = parseProperty(propertySymbol, project);
});
await Promise.all(
propertiesOnProject.map(async (propertySymbol) => {
properties[propertySymbol.name] = await parseProperty(propertySymbol, project);
}),
);

result = Object.values(properties)
.filter((property) => !property.tags.ignore)
Expand Down Expand Up @@ -536,8 +544,8 @@ export default async function generateHookApi(
{ filename },
);

const parameters = extractInfoFromType(`${upperFirst(name)}Parameters`, project);
const returnValue = extractInfoFromType(`${upperFirst(name)}ReturnValue`, project);
const parameters = await extractInfoFromType(`${upperFirst(name)}Parameters`, project);
const returnValue = await extractInfoFromType(`${upperFirst(name)}ReturnValue`, project);

// Ignore what we might have generated in `annotateHookDefinition`
const annotatedDescriptionMatch = reactApi.description.match(/(Demos|API):\r?\n\r?\n/);
Expand Down Expand Up @@ -572,12 +580,12 @@ export default async function generateHookApi(

if (!skipApiGeneration) {
// Generate pages, json and translations
generateApiTranslations(
await generateApiTranslations(
path.join(process.cwd(), 'docs/translations/api-docs'),
reactApi,
projectSettings.translationLanguages,
);
generateApiJson(apiPagesDirectory, reactApi);
await generateApiJson(apiPagesDirectory, reactApi);

// Add comment about demo & api links to the component hook file
await annotateHookDefinition(reactApi);
Expand Down
2 changes: 1 addition & 1 deletion packages/api-docs-builder/ProjectSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface ProjectSettings {
/**
* Callback function to be called when the API generation is completed
*/
onCompleted?: () => void;
onCompleted?: () => void | Promise<void>;
/**
* Callback to customize the manifest file before it's written to the disk
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/api-docs-builder/buildApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ async function buildSingleProject(
source = projectSettings.onWritingManifestFile(builds, source);
}

writePrettifiedFile(apiPagesManifestPath, source);
await writePrettifiedFile(apiPagesManifestPath, source);

projectSettings.onCompleted?.();
await projectSettings.onCompleted?.();
return builds;
}
Loading