Skip to content

Commit

Permalink
[core] Generate propTypes (#2395)
Browse files Browse the repository at this point in the history
  • Loading branch information
m4theushw authored Sep 8, 2021
1 parent 9067717 commit 253400f
Show file tree
Hide file tree
Showing 48 changed files with 2,503 additions and 305 deletions.
6 changes: 6 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ jobs:
- run:
name: Lint JSON
command: yarn jsonlint
- run:
name: Generate PropTypes
command: yarn proptypes
- run:
name: '`yarn proptypes` changes committed?'
command: git diff --exit-code
- run:
name: Generate the documentation
command: yarn docs:api
Expand Down
7 changes: 6 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ module.exports = {
},
],
],
ignore: [/@babel[\\|/]runtime/], // Fix a Windows issue.
ignore: [
// Fix a Windows issue.
/@babel[\\|/]runtime/,
// Fix const foo = /{{(.+?)}}/gs; crashing.
/prettier/,
],
env: {
coverage: {},
test: {
Expand Down
149 changes: 149 additions & 0 deletions docs/scripts/generateProptypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import * as yargs from 'yargs';
import * as path from 'path';
import * as fse from 'fs-extra';
import * as prettier from 'prettier';
import * as ttp from '@material-ui/monorepo/packages/typescript-to-proptypes/src';
import { fixBabelGeneratorIssues, fixLineEndings } from 'docs/scripts/helpers';

const tsconfig = ttp.loadConfig(path.resolve(__dirname, '../../tsconfig.json'));

const prettierConfig = prettier.resolveConfig.sync(process.cwd(), {
config: path.join(__dirname, '../../prettier.config.js'),
});

async function generateProptypes(program: ttp.ts.Program, sourceFile: string) {
const proptypes = ttp.parseFromProgram(sourceFile, program, {
checkDeclarations: true,
shouldResolveObject: ({ name }) => {
const propsToNotResolve = [
'classes',
'components',
'componentsProps',
'columns',
'currentColumn',
'colDef',
];
if (propsToNotResolve.includes(name)) {
return false;
}
return undefined;
},
});

if (proptypes.body.length === 0) {
return;
}

const sourceContent = await fse.readFile(sourceFile, 'utf8');

const result = ttp.inject(proptypes, sourceContent, {
disablePropTypesTypeChecking: true,
comment: [
'----------------------------- Warning --------------------------------',
'| These PropTypes are generated from the TypeScript type definitions |',
'| To update them edit the TypeScript types and run "yarn proptypes" |',
'----------------------------------------------------------------------',
].join('\n'),
reconcilePropTypes: (prop, previous, generated) => {
const usedCustomValidator = previous !== undefined && !previous.startsWith('PropTypes');
const ignoreGenerated =
previous !== undefined &&
previous.startsWith('PropTypes /* @typescript-to-proptypes-ignore */');
return usedCustomValidator || ignoreGenerated ? previous! : generated;
},
shouldInclude: ({ component, prop }) => {
if (['children', 'state'].includes(prop.name) && component.name.startsWith('DataGrid')) {
return false;
}
let shouldDocument = true;
prop.filenames.forEach((filename) => {
// Don't include props from external dependencies
if (/node_modules/.test(filename)) {
shouldDocument = false;
}
});
return shouldDocument;
},
});

if (!result) {
throw new Error('Unable to produce inject propTypes into code.');
}

const prettified = prettier.format(result, { ...prettierConfig, filepath: sourceFile });
const formatted = fixBabelGeneratorIssues(prettified);
const correctedLineEndings = fixLineEndings(sourceContent, formatted);

await fse.writeFile(sourceFile, correctedLineEndings);
}

function findComponents(folderPath) {
const files = fse.readdirSync(folderPath, { withFileTypes: true });
return files.reduce((acc, file) => {
if (file.isDirectory()) {
const filesInFolder = findComponents(path.join(folderPath, file.name));
return [...acc, ...filesInFolder];
}
if (/[A-Z]+.*\.tsx/.test(file.name)) {
return [...acc, path.join(folderPath, file.name)];
}
return acc;
}, []);
}

async function run() {
const componentsToAddPropTypes = [
path.resolve(__dirname, '../../packages/grid/data-grid/src/DataGrid.tsx'),
path.resolve(__dirname, '../../packages/grid/x-grid/src/DataGridPro.tsx'),
];

const indexPath = path.resolve(__dirname, '../../packages/grid/_modules_/index.ts');
const program = ttp.createTSProgram([...componentsToAddPropTypes, indexPath], tsconfig);
const checker = program.getTypeChecker();
const indexFile = program.getSourceFile(indexPath)!;
const symbol = checker.getSymbolAtLocation(indexFile);
const exports = checker.getExportsOfModule(symbol!);

const componentsFolder = path.resolve(__dirname, '../../packages/grid/_modules_/grid/components');
const components = findComponents(componentsFolder);
components.forEach((component) => {
const componentName = path.basename(component).replace('.tsx', '');
const isExported = exports.find((e) => e.name === componentName);
if (isExported) {
componentsToAddPropTypes.push(component);
}
});

const promises = componentsToAddPropTypes.map<Promise<void>>(async (file) => {
try {
await generateProptypes(program, file);
} catch (error: any) {
error.message = `${file}: ${error.message}`;
throw error;
}
});

const results = await Promise.allSettled(promises);

const fails = results.filter((result): result is PromiseRejectedResult => {
return result.status === 'rejected';
});

fails.forEach((result) => {
console.error(result.reason);
});
if (fails.length > 0) {
process.exit(1);
}
}

yargs
.command({
command: '$0',
describe: 'Generates Component.propTypes from TypeScript declarations',
handler: run,
})
.help()
.strict(true)
.version(false)
.parse();
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"stylelint": "stylelint '**/*.js' '**/*.ts' '**/*.tsx'",
"prettier": "node ./scripts/prettier.js --branch next",
"prettier:all": "node ./scripts/prettier.js write",
"proptypes": "cross-env BABEL_ENV=development babel-node -i \"/node_modules/(?!@material-ui)/\" -x .ts,.tsx,.js ./docs/scripts/generateProptypes.ts",
"size:snapshot": "node --max-old-space-size=2048 ./scripts/sizeSnapshot/create",
"size:why": "yarn size:snapshot --analyze --accurateBundles",
"test": "lerna run test --parallel",
Expand Down Expand Up @@ -61,6 +62,7 @@
"@material-ui/core": "^4.9.12",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.54",
"@material-ui/utils": "^5.0.0-beta.5",
"@material-ui/monorepo": "/~https://github.com/mui-org/material-ui.git#next",
"@material-ui/unstyled": "next",
"@rollup/plugin-node-resolve": "^13.0.4",
Expand Down
41 changes: 40 additions & 1 deletion packages/grid/_modules_/grid/components/GridAutoSizer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { useForkRef, ownerWindow } from '@material-ui/core/utils';
import { useEventCallback, useEnhancedEffect } from '../utils/material-ui-utils';
import createDetectElementResize from '../lib/createDetectElementResize';
Expand Down Expand Up @@ -50,7 +51,7 @@ export interface AutoSizerProps extends Omit<React.HTMLAttributes<HTMLDivElement
onResize?: (size: AutoSizerSize) => void;
}

export const GridAutoSizer = React.forwardRef<HTMLDivElement, AutoSizerProps>(function AutoSizer(
const GridAutoSizer = React.forwardRef<HTMLDivElement, AutoSizerProps>(function AutoSizer(
props,
ref,
) {
Expand Down Expand Up @@ -156,3 +157,41 @@ export const GridAutoSizer = React.forwardRef<HTMLDivElement, AutoSizerProps>(fu
</div>
);
});

GridAutoSizer.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* Default height to use for initial render; useful for SSR.
* @default null
*/
defaultHeight: PropTypes.number,
/**
* Default width to use for initial render; useful for SSR.
* @default null
*/
defaultWidth: PropTypes.number,
/**
* If `true`, disable dynamic :height property.
* @default false
*/
disableHeight: PropTypes.bool,
/**
* If `true`, disable dynamic :width property.
* @default false
*/
disableWidth: PropTypes.bool,
/**
* Nonce of the inlined stylesheet for Content Security Policy.
*/
nonce: PropTypes.string,
/**
* Callback to be invoked on-resize.
* @param {AutoSizerSize} size The grid's size.
*/
onResize: PropTypes.func,
} as any;

export { GridAutoSizer };
2 changes: 2 additions & 0 deletions packages/grid/_modules_/grid/components/GridPagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const GridPagination = React.forwardRef<
() => Math.floor(paginationState.rowCount / (paginationState.pageSize || 1)),
[paginationState.rowCount, paginationState.pageSize],
);

const handlePageSizeChange = React.useCallback(
(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
const newPageSize = Number(event.target.value);
Expand Down Expand Up @@ -95,6 +96,7 @@ export const GridPagination = React.forwardRef<
`Add it to show the pagination select.`,
].join('\n'),
);

warnedOnceMissingPageSizeInRowsPerPageOptions.current = true;
}
}
Expand Down
20 changes: 19 additions & 1 deletion packages/grid/_modules_/grid/components/GridRenderingZone.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { ElementSize } from '../models';
import { getDataGridUtilityClass } from '../gridClasses';
import { useGridRootProps } from '../hooks/utils/useGridRootProps';
Expand All @@ -19,7 +20,7 @@ const useUtilityClasses = (ownerState: OwnerState) => {
return composeClasses(slots, getDataGridUtilityClass, classes);
};

export const GridRenderingZone = React.forwardRef<HTMLDivElement, ElementSize & WithChildren>(
const GridRenderingZone = React.forwardRef<HTMLDivElement, ElementSize & WithChildren>(
function GridRenderingZone(props, ref) {
const { height, width, children } = props;
const rootProps = useGridRootProps();
Expand All @@ -39,3 +40,20 @@ export const GridRenderingZone = React.forwardRef<HTMLDivElement, ElementSize &
);
},
);

GridRenderingZone.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* The height of a container or HTMLElement.
*/
height: PropTypes.number.isRequired,
/**
* The width of a container or HTMLElement.
*/
width: PropTypes.number.isRequired,
} as any;

export { GridRenderingZone };
16 changes: 15 additions & 1 deletion packages/grid/_modules_/grid/components/GridRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { GridEvents } from '../constants/eventsConstants';
import { GridRowId } from '../models/gridRows';
Expand Down Expand Up @@ -34,7 +35,7 @@ const useUtilityClasses = (ownerState: OwnerState) => {
return composeClasses(slots, getDataGridUtilityClass, classes);
};

export function GridRow(props: GridRowProps) {
function GridRow(props: GridRowProps) {
const { selected, id, rowIndex, children } = props;
const ariaRowIndex = rowIndex + 2; // 1 for the header row and 1 as it's 1 based
const apiRef = useGridApiContext();
Expand Down Expand Up @@ -109,3 +110,16 @@ export function GridRow(props: GridRowProps) {
</div>
);
}

GridRow.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
children: PropTypes.node,
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
rowIndex: PropTypes.number.isRequired,
selected: PropTypes.bool.isRequired,
} as any;

export { GridRow };
Loading

0 comments on commit 253400f

Please sign in to comment.