From bf95fb73a41e3e18748bdfcdf421e5b7b249d1ee Mon Sep 17 00:00:00 2001 From: Raman Marozau Date: Thu, 12 Sep 2024 17:29:45 +0200 Subject: [PATCH 1/3] [bugfix] Refactor prop types generation and add import optimization Add type annotations to enhance the robustness of prop type generation. Introduce a new `optimiseImports` function to standardize import formats. Implement an `optimise` command for removing unnecessary imports in declaration files. --- scripts/generateProptypes.ts | 126 +++++++++++++++++++++++++++++++++-- 1 file changed, 121 insertions(+), 5 deletions(-) diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts index c75ec6f11e0cdd..2ce154156f79cb 100644 --- a/scripts/generateProptypes.ts +++ b/scripts/generateProptypes.ts @@ -213,7 +213,7 @@ async function generateProptypes( }); }); - const sourceContent = await fse.readFile(sourceFile, 'utf8'); + const sourceContent: string = await fse.readFile(sourceFile, 'utf8'); const isTsFile = /(\.(ts|tsx))/.test(sourceFile); // If the component inherits the props from some unstyled components // we don't want to add those propTypes again in the Material UI/Joy UI propTypes @@ -224,6 +224,7 @@ async function generateProptypes( const propsFile = tsFile.replace(/(\.d\.ts|\.tsx|\.ts)/g, 'Props.ts'); const propsFileAlternative = tsFile.replace(/(\.d\.ts|\.tsx|\.ts)/g, '.types.ts'); const generatedForTypeScriptFile = sourceFile === tsFile; + const result = injectPropTypesInFile({ components, target: sourceContent, @@ -244,7 +245,7 @@ async function generateProptypes( getSortLiteralUnions, reconcilePropTypes: (prop, previous, generated) => { const usedCustomValidator = previous !== undefined && !previous.startsWith('PropTypes'); - const ignoreGenerated = + const ignoreGenerated: boolean = previous !== undefined && previous.startsWith('PropTypes /* @typescript-to-proptypes-ignore */'); @@ -313,9 +314,43 @@ async function generateProptypes( await fse.writeFile(sourceFile, correctedLineEndings); } + +/** + * Generates utils types for a given source file. + * + * @param {string} sourceFile - The path of the source file to generate utils types for. + * @return {Promise} - A Promise that resolves when the utils types are generated and written to the source file. + * @throws {Error} - If unable to produce inject optimise TS imports into code. + */ +async function optimiseImports( + sourceFile: string, +): Promise { + + const sourceContent: string = await fse.readFile(sourceFile, 'utf8'); + + + const result = sourceContent + .replace('import PropTypes from \'prop-types\';', 'import * as PropTypes from \'prop-types\';') + + if (!result) { + throw new Error('Unable to produce inject optimise TS imports into code.'); + } + + 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); + + await fse.writeFile(sourceFile, correctedLineEndings); +} + interface HandlerArgv { pattern: string; } + async function run(argv: HandlerArgv) { const { pattern } = argv; @@ -376,11 +411,80 @@ async function run(argv: HandlerArgv) { const results = await Promise.allSettled(promises); - const fails = results.filter((result): result is PromiseRejectedResult => { + const fails: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => { + return result.status === 'rejected'; + }); + + fails.forEach((result: PromiseRejectedResult) => { + console.error(result.reason); + }); + if (fails.length > 0) { + process.exit(1); + } +} + + + +/** + * Optimises declaration files by removing unnecessary imports. + * + * @param {HandlerArgv} argv - The arguments object containing the pattern to match declaration files. + * @returns {Promise} - A promise that resolves when the optimisation is complete. + */ +async function optimise(argv: HandlerArgv) { + const { pattern } = argv; + + const filePattern = new RegExp(pattern); + if (pattern.length > 0) { + console.log(`Only considering declaration files matching ${filePattern}`); + } + + const allBuiltFiles = await Promise.all( + [ + path.resolve(__dirname, '../packages/mui-utils/build'), + ].map((folderPath) => { + return glob('+([a-z])*/+([a-z])*.*@(d.ts)', { + absolute: true, + cwd: folderPath, + }); + }), + ); + + const builtFiles = _.flatten(allBuiltFiles) + .filter((filePath) => { + // Filter out files where the directory name and filename doesn't match + // Example: refType/refType.d.ts + let folderName = path.basename(path.dirname(filePath)); + const fileName = path.basename(filePath).replace(/(\.d\.ts)/g, ''); + + // An exception is if the folder name starts with Unstable_/unstable_ + // Example: unstable_refType/refType.tsx + // @note: Consider whether it can have no value for utils + if (/(u|U)nstable_/g.test(folderName)) { + folderName = folderName.slice(9); + } + + return fileName === folderName; + }) + .filter((filePath: string) => filePattern.test(filePath)); + + const promises = builtFiles + .map>(async (file: string): Promise => { + try { + await optimiseImports(file); + } catch (error: any) { + error.message = `${file}: ${error.message}`; + throw error; + } + }); + + const results = await Promise.allSettled(promises); + + const fails: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => { return result.status === 'rejected'; }); - fails.forEach((result) => { + fails.forEach((result: PromiseRejectedResult) => { console.error(result.reason); }); if (fails.length > 0) { @@ -399,7 +503,19 @@ yargs type: 'string', }); }, - handler: run, + handler: run + }) + .command({ + command: '$0', + describe: 'Optimise Utils PropTypes from TypeScript declarations', + builder: (command) => { + return command.option('pattern', { + default: '', + describe: 'Only considers declaration files matching this pattern.', + type: 'string', + }); + }, + handler: optimise, }) .help() .strict(true) From 17d03352733b860b5dbf536f9a00983a238530dd Mon Sep 17 00:00:00 2001 From: Raman Marozau Date: Thu, 12 Sep 2024 22:53:02 +0200 Subject: [PATCH 2/3] [bugfix] Update comment example in PropTypes generation script Correct the example file name in the comment to reflect the proper '.d.ts' extension, ensuring clarity and accuracy for future reference. No functional changes made; purely a documentation update. --- scripts/generateProptypes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts index 2ce154156f79cb..a4b461e4134c0d 100644 --- a/scripts/generateProptypes.ts +++ b/scripts/generateProptypes.ts @@ -458,8 +458,8 @@ async function optimise(argv: HandlerArgv) { const fileName = path.basename(filePath).replace(/(\.d\.ts)/g, ''); // An exception is if the folder name starts with Unstable_/unstable_ - // Example: unstable_refType/refType.tsx - // @note: Consider whether it can have no value for utils + // Example: unstable_refType/refType.d.ts + // @note: whether utils might not require this value if (/(u|U)nstable_/g.test(folderName)) { folderName = folderName.slice(9); } From 98b06d6d912ca095b04ed9f2d7b46d4cf696e908 Mon Sep 17 00:00:00 2001 From: Raman Marozau Date: Fri, 13 Sep 2024 17:42:47 +0200 Subject: [PATCH 3/3] [bugfix] Refactor optimise functions and add separate commands Renamed and refactored optimisation functions for clarity. Introduced distinct handling commands for optimizing PropTypes and TypeScript declaration files. Added error logging and better pattern matching in both command handlers. --- scripts/generateProptypes.ts | 178 ++++++++++++++++++++++++++++------- 1 file changed, 142 insertions(+), 36 deletions(-) diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts index a4b461e4134c0d..a10647dfc814ec 100644 --- a/scripts/generateProptypes.ts +++ b/scripts/generateProptypes.ts @@ -315,38 +315,6 @@ async function generateProptypes( } -/** - * Generates utils types for a given source file. - * - * @param {string} sourceFile - The path of the source file to generate utils types for. - * @return {Promise} - A Promise that resolves when the utils types are generated and written to the source file. - * @throws {Error} - If unable to produce inject optimise TS imports into code. - */ -async function optimiseImports( - sourceFile: string, -): Promise { - - const sourceContent: string = await fse.readFile(sourceFile, 'utf8'); - - - const result = sourceContent - .replace('import PropTypes from \'prop-types\';', 'import * as PropTypes from \'prop-types\';') - - if (!result) { - throw new Error('Unable to produce inject optimise TS imports into code.'); - } - - 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); - - await fse.writeFile(sourceFile, correctedLineEndings); -} - interface HandlerArgv { pattern: string; } @@ -424,6 +392,37 @@ async function run(argv: HandlerArgv) { } +/** + * Generates utils types for a given source file. + * + * @param {string} sourceFile - The path of the source file to generate utils types for. + * @return {Promise} - A Promise that resolves when the utils types are generated and written to the source file. + * @throws {Error} - If unable to produce inject optimise TS imports into code. + */ +async function optimisePropTypesImports( + sourceFile: string, +): Promise { + + const sourceContent: string = await fse.readFile(sourceFile, 'utf8'); + + + const result = sourceContent + .replace('import PropTypes from \'prop-types\';', 'import * as PropTypes from \'prop-types\';') + + if (!result) { + throw new Error('Unable to produce inject optimise TS imports into code.'); + } + + 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); + + await fse.writeFile(sourceFile, correctedLineEndings); +} /** * Optimises declaration files by removing unnecessary imports. @@ -431,7 +430,7 @@ async function run(argv: HandlerArgv) { * @param {HandlerArgv} argv - The arguments object containing the pattern to match declaration files. * @returns {Promise} - A promise that resolves when the optimisation is complete. */ -async function optimise(argv: HandlerArgv) { +async function optimisePropTypes(argv: HandlerArgv) { const { pattern } = argv; const filePattern = new RegExp(pattern); @@ -471,7 +470,102 @@ async function optimise(argv: HandlerArgv) { const promises = builtFiles .map>(async (file: string): Promise => { try { - await optimiseImports(file); + await optimisePropTypesImports(file); + } catch (error: any) { + error.message = `${file}: ${error.message}`; + throw error; + } + }); + + const results = await Promise.allSettled(promises); + + const fails: PromiseRejectedResult[] = results.filter((result): result is PromiseRejectedResult => { + return result.status === 'rejected'; + }); + + fails.forEach((result: PromiseRejectedResult) => { + console.error(result.reason); + }); + if (fails.length > 0) { + process.exit(1); + } +} + +/** + * Generates utils types for a given source file. + * + * @param {string} sourceFile - The path of the source file to generate utils types for. + * @return {Promise} - A Promise that resolves when the utils types are generated and written to the source file. + * @throws {Error} - If unable to produce inject optimise TS imports into code. + */ +async function optimiseTsImports( + sourceFile: string, +): Promise { + + const sourceContent: string = await fse.readFile(sourceFile, 'utf8'); + + + const result = sourceContent + .replace('export { default } from \'./shadows\';', 'export * from \'./shadows\';') + .replace(/export \{ default } from '.\/([a-z]+)';/, '\n'); + + + if (!result) { + throw new Error('Unable to produce inject optimise TS imports into code.'); + } + + 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); + + await fse.writeFile(sourceFile, correctedLineEndings); +} + +/** + * Optimises TypeScript declaration files. + * + * @param {*} argv - The command line arguments. + */ +async function optimiseTs(argv: HandlerArgv) { + const { pattern } = argv; + + const filePattern = new RegExp(pattern); + if (pattern.length > 0) { + console.log(`Only considering declaration files matching ${filePattern}`); + } + + const allBuiltFiles = await Promise.all( + [ + path.resolve(__dirname, '../packages/mui-system/build'), + ].map((folderPath) => { + return glob('+([a-z]+)/+([index.d.ts])', { + absolute: true, + cwd: folderPath, + }); + }), + ); + + const builtFiles = _.flatten(allBuiltFiles) + .filter((filePath) => { + // Filter out files where the directory name and filename doesn't match + // Example: shadows/index.d.ts + const fileName = path.basename(filePath).replace(/(\.d\.ts)/g, ''); + console.log('fileName: ', fileName); + + return fileName === 'index'; + }) + .filter((filePath: string) => filePattern.test(filePath)); + + console.log('builtFiles: ', builtFiles); + + const promises = builtFiles + .map>(async (file: string): Promise => { + try { + await optimiseTsImports(file); } catch (error: any) { error.message = `${file}: ${error.message}`; throw error; @@ -507,7 +601,19 @@ yargs }) .command({ command: '$0', - describe: 'Optimise Utils PropTypes from TypeScript declarations', + describe: 'Optimise Utils PropTypes for TypeScript declarations', + builder: (command) => { + return command.option('pattern', { + default: '', + describe: 'Only considers declaration files matching this pattern.', + type: 'string', + }); + }, + handler: optimisePropTypes, + }) + .command({ + command: '$0', + describe: 'Optimise System Types for TypeScript declarations', builder: (command) => { return command.option('pattern', { default: '', @@ -515,7 +621,7 @@ yargs type: 'string', }); }, - handler: optimise, + handler: optimiseTs, }) .help() .strict(true)