diff --git a/docs/scripts/formattedTSDemos.js b/docs/scripts/formattedTSDemos.js index b0f317e3007421..31e9cce36ea25d 100644 --- a/docs/scripts/formattedTSDemos.js +++ b/docs/scripts/formattedTSDemos.js @@ -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); diff --git a/packages/api-docs-builder-core/baseUi/generateBaseUiApiPages.ts b/packages/api-docs-builder-core/baseUi/generateBaseUiApiPages.ts index c427ab58ffcd09..929ada80e915a5 100644 --- a/packages/api-docs-builder-core/baseUi/generateBaseUiApiPages.ts +++ b/packages/api-docs-builder-core/baseUi/generateBaseUiApiPages.ts @@ -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'; @@ -41,32 +42,32 @@ 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, @@ -74,16 +75,16 @@ Page.getLayout = (page) => { ); 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, @@ -91,27 +92,27 @@ Page.getLayout = (page) => { ); 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'; @@ -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, - ); - } - }); + }), + ); } diff --git a/packages/api-docs-builder-core/baseUi/projectSettings.ts b/packages/api-docs-builder-core/baseUi/projectSettings.ts index bd65a7e66f55c9..b9b64d42adc3c4 100644 --- a/packages/api-docs-builder-core/baseUi/projectSettings.ts +++ b/packages/api-docs-builder-core/baseUi/projectSettings.ts @@ -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); diff --git a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts index db0758fa1c6ed6..8a425543ce8e3d 100644 --- a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts +++ b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts @@ -354,7 +354,7 @@ function extractClassCondition(description: string) { return { description: renderMarkdown(description) }; } -const generateApiPage = ( +const generateApiPage = async ( apiPagesDirectory: string, importTranslationPagesDirectory: string, reactApi: ReactApi, @@ -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'; @@ -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, diff --git a/packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts index d1552a9535bb74..e9d357164e67cd 100644 --- a/packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts +++ b/packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts @@ -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 => ({ 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 { @@ -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. */ @@ -430,13 +433,16 @@ const generateApiJson = (outputDirectory: string, reactApi: ReactApi) => { .join('\n')}`, }; - 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 => { // Generate the params let result: ParsedProperty[] = []; @@ -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) @@ -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/); @@ -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); diff --git a/packages/api-docs-builder/ProjectSettings.ts b/packages/api-docs-builder/ProjectSettings.ts index a46a8b2c6f5ff2..f7a210a03b1377 100644 --- a/packages/api-docs-builder/ProjectSettings.ts +++ b/packages/api-docs-builder/ProjectSettings.ts @@ -40,7 +40,7 @@ export interface ProjectSettings { /** * Callback function to be called when the API generation is completed */ - onCompleted?: () => void; + onCompleted?: () => void | Promise; /** * Callback to customize the manifest file before it's written to the disk */ diff --git a/packages/api-docs-builder/buildApi.ts b/packages/api-docs-builder/buildApi.ts index b1ff5c5869c409..3ea03e866ab344 100644 --- a/packages/api-docs-builder/buildApi.ts +++ b/packages/api-docs-builder/buildApi.ts @@ -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; } diff --git a/packages/api-docs-builder/buildApiUtils.ts b/packages/api-docs-builder/buildApiUtils.ts index 29297d54e4440c..6afbfdd2d99565 100644 --- a/packages/api-docs-builder/buildApiUtils.ts +++ b/packages/api-docs-builder/buildApiUtils.ts @@ -35,13 +35,13 @@ export function fixPathname(pathname: string): string { const DEFAULT_PRETTIER_CONFIG_PATH = path.join(process.cwd(), 'prettier.config.js'); -export function writePrettifiedFile( +export async function writePrettifiedFile( filename: string, data: string, prettierConfigPath: string = DEFAULT_PRETTIER_CONFIG_PATH, options: object = {}, ) { - const prettierConfig = prettier.resolveConfig.sync(filename, { + const prettierConfig = await prettier.resolveConfig(filename, { config: prettierConfigPath, }); if (prettierConfig === null) { @@ -50,7 +50,8 @@ export function writePrettifiedFile( ); } - fs.writeFileSync(filename, prettier.format(data, { ...prettierConfig, filepath: filename }), { + const formatted = await prettier.format(data, { ...prettierConfig, filepath: filename }); + fs.writeFileSync(filename, formatted, { encoding: 'utf8', ...options, }); @@ -181,7 +182,7 @@ export function getApiPath( return apiPath; } -export function formatType(rawType: string) { +export async function formatType(rawType: string) { if (!rawType) { return ''; } @@ -189,7 +190,7 @@ export function formatType(rawType: string) { const prefix = 'type FakeType = '; const signatureWithTypeName = `${prefix}${rawType}`; - const prettifiedSignatureWithTypeName = prettier.format(signatureWithTypeName, { + const prettifiedSignatureWithTypeName = await prettier.format(signatureWithTypeName, { printWidth: 999, singleQuote: true, semi: false, @@ -212,7 +213,7 @@ export function getSymbolJSDocTags(symbol: ts.Symbol) { return Object.fromEntries(symbol.getJsDocTags().map((tag) => [tag.name, tag])); } -export function stringifySymbol(symbol: ts.Symbol, project: TypeScriptProject) { +export async function stringifySymbol(symbol: ts.Symbol, project: TypeScriptProject) { let rawType: string; const declaration = symbol.declarations?.[0]; diff --git a/packages/api-docs-builder/utils/generateApiTranslation.ts b/packages/api-docs-builder/utils/generateApiTranslation.ts index 116c5f08cf0f29..50b6aa0dbe9c9c 100644 --- a/packages/api-docs-builder/utils/generateApiTranslation.ts +++ b/packages/api-docs-builder/utils/generateApiTranslation.ts @@ -9,7 +9,7 @@ interface MinimalReactAPI { translations: object; } -export default function generateApiTranslations( +export default async function generateApiTranslations( outputDirectory: string, reactApi: ReactApi, languages: string[], @@ -30,23 +30,25 @@ export default function generateApiTranslations { - if (language !== 'en') { - try { - writePrettifiedFile( - resolveApiDocsTranslationsComponentLanguagePath(language), - JSON.stringify(reactApi.translations), - undefined, - { flag: 'wx' }, - ); - } catch (error) { - // File exists + await Promise.all( + languages.map(async (language) => { + if (language !== 'en') { + try { + await writePrettifiedFile( + resolveApiDocsTranslationsComponentLanguagePath(language), + JSON.stringify(reactApi.translations), + undefined, + { flag: 'wx' }, + ); + } catch (error) { + // File exists + } } - } - }); + }), + ); } diff --git a/packages/typescript-to-proptypes/test/typescript-to-proptypes.test.ts b/packages/typescript-to-proptypes/test/typescript-to-proptypes.test.ts index 9a47db1af5d892..afeca522b5d596 100644 --- a/packages/typescript-to-proptypes/test/typescript-to-proptypes.test.ts +++ b/packages/typescript-to-proptypes/test/typescript-to-proptypes.test.ts @@ -110,8 +110,8 @@ describe('typescript-to-proptypes', () => { result = injected; } - const prettierConfig = prettier.resolveConfig.sync(outputPath); - const propTypes = prettier.format(result, { + const prettierConfig = await prettier.resolveConfig(outputPath); + const propTypes = await prettier.format(result, { ...prettierConfig, filepath: outputPath, }); diff --git a/prettier.config.js b/prettier.config.js index 067b11365f648f..136646bca909ee 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -19,5 +19,11 @@ module.exports = { printWidth: 82, }, }, + { + files: ['**/*.json'], + options: { + trailingComma: 'none', + }, + }, ], }; diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts index eb23828a87e557..c0409553545ed3 100644 --- a/scripts/generateProptypes.ts +++ b/scripts/generateProptypes.ts @@ -162,10 +162,6 @@ const getSortLiteralUnions: InjectPropTypesInFileOptions['getSortLiteralUnions'] return undefined; }; -const prettierConfig = prettier.resolveConfig.sync(process.cwd(), { - config: path.join(__dirname, '../prettier.config.js'), -}); - async function generateProptypes( project: TypeScriptProject, sourceFile: string, @@ -306,7 +302,11 @@ async function generateProptypes( throw new Error('Unable to produce inject propTypes into code.'); } - const prettified = prettier.format(result, { ...prettierConfig, filepath: sourceFile }); + const prettierConfig = await prettier.resolveConfig(process.cwd(), { + config: path.join(__dirname, '../prettier.config.js'), + }); + + const prettified = await prettier.format(result, { ...prettierConfig, filepath: sourceFile }); const formatted = fixBabelGeneratorIssues(prettified); const correctedLineEndings = fixLineEndings(sourceContent, formatted);