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

[bugfix] Refactor prop types generation and add import optimisation #43736

Closed
Changes from all 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
232 changes: 227 additions & 5 deletions scripts/generateProptypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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 */');

Expand Down Expand Up @@ -313,9 +314,11 @@ async function generateProptypes(
await fse.writeFile(sourceFile, correctedLineEndings);
}


interface HandlerArgv {
pattern: string;
}

async function run(argv: HandlerArgv) {
const { pattern } = argv;

Expand Down Expand Up @@ -376,11 +379,206 @@ 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);
}
}


/**
* Generates utils types for a given source file.
*
* @param {string} sourceFile - The path of the source file to generate utils types for.
* @return {Promise<void>} - 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<void> {

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.
*
* @param {HandlerArgv} argv - The arguments object containing the pattern to match declaration files.
* @returns {Promise<void>} - A promise that resolves when the optimisation is complete.
*/
async function optimisePropTypes(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.d.ts
// @note: whether utils might not require this value
if (/(u|U)nstable_/g.test(folderName)) {
folderName = folderName.slice(9);
}

return fileName === folderName;
})
.filter((filePath: string) => filePattern.test(filePath));

const promises = builtFiles
.map<Promise<void>>(async (file: string): Promise<void> => {
try {
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<void>} - 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<void> {

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<Promise<void>>(async (file: string): Promise<void> => {
try {
await optimiseTsImports(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) {
Expand All @@ -399,7 +597,31 @@ yargs
type: 'string',
});
},
handler: run,
handler: run
})
.command<HandlerArgv>({
command: '$0',
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<HandlerArgv>({
command: '$0',
describe: 'Optimise System Types for TypeScript declarations',
builder: (command) => {
return command.option('pattern', {
default: '',
describe: 'Only considers declaration files matching this pattern.',
type: 'string',
});
},
handler: optimiseTs,
})
.help()
.strict(true)
Expand Down