Skip to content

Commit

Permalink
[code-infra] Migrate to prettier async APIs (#40668)
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Potoms <2109932+Janpot@users.noreply.github.com>
  • Loading branch information
Janpot authored Jan 24, 2024
1 parent 8c039dd commit b63fb95
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 134 deletions.
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

0 comments on commit b63fb95

Please sign in to comment.