Skip to content

Commit 6445fb2

Browse files
[core] Move utils package to TypeScript (#22367)
1 parent 02b722a commit 6445fb2

27 files changed

+196
-128
lines changed

.eslintrc.js

+2
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ module.exports = {
131131
files: [
132132
// matching the pattern of the test runner
133133
'*.test.js',
134+
'*.test.ts',
135+
'*.test.tsx',
134136
],
135137
env: {
136138
mocha: true,
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
22
"extends": "../../tsconfig",
3+
"compilerOptions": {
4+
"types": ["node"]
5+
},
36
"include": ["src/**/*"]
47
}

packages/material-ui-utils/package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": false,
55
"author": "Material-UI Team",
66
"description": "Material-UI Utils - Utility functions for Material-UI.",
7-
"main": "./src/index.js",
7+
"main": "./src/index.ts",
88
"keywords": [
99
"react",
1010
"react-component",
@@ -23,21 +23,25 @@
2323
},
2424
"homepage": "https://github.com/mui-org/material-ui/tree/next/packages/material-ui-utils",
2525
"scripts": {
26-
"build": "yarn build:cjs && yarn build:esm && yarn build:es && yarn build:copy-files",
26+
"build": "yarn build:cjs && yarn build:esm && yarn build:es && yarn build:copy-files && yarn build:types",
2727
"build:cjs": "node ../../scripts/build cjs",
2828
"build:esm": "node ../../scripts/build esm",
2929
"build:es": "node ../../scripts/build es",
3030
"build:copy-files": "node ../../scripts/copy-files.js",
31+
"build:types": "tsc -p tsconfig.build.json",
3132
"prebuild": "rimraf build",
3233
"release": "yarn build && npm publish build --tag next",
33-
"test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/material-ui-utils/**/*.test.{js,ts,tsx}' --exclude '**/node_modules/**'"
34+
"test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/material-ui-utils/**/*.test.{js,ts,tsx}' --exclude '**/node_modules/**'",
35+
"typescript": "tslint -p tsconfig.json \"{src,test}/**/*.{spec,d}.{ts,tsx}\" && tsc -p tsconfig.json"
3436
},
3537
"peerDependencies": {
3638
"react": "^16.8.0",
3739
"react-dom": "^16.8.0"
3840
},
3941
"dependencies": {
4042
"@babel/runtime": "^7.4.4",
43+
"@types/prop-types": "^15.7.3",
44+
"@types/react-is": "^16.7.1",
4145
"prop-types": "^15.7.2",
4246
"react-is": "^16.8.0"
4347
},

packages/material-ui-utils/src/HTMLElementType.js packages/material-ui-utils/src/HTMLElementType.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
export default function HTMLElementType(props, propName, componentName, location, propFullName) {
1+
export default function HTMLElementType(
2+
props: { [key: string]: unknown },
3+
propName: string,
4+
componentName: string,
5+
location: string,
6+
propFullName: string,
7+
): Error | null {
28
if (process.env.NODE_ENV === 'production') {
39
return null;
410
}
@@ -10,7 +16,7 @@ export default function HTMLElementType(props, propName, componentName, location
1016
return null;
1117
}
1218

13-
if (propValue && propValue.nodeType !== 1) {
19+
if (propValue && (propValue as any).nodeType !== 1) {
1420
return new Error(
1521
`Invalid ${location} \`${safePropName}\` supplied to \`${componentName}\`. ` +
1622
`Expected an HTMLElement.`,

packages/material-ui-utils/src/chainPropTypes.js

-9
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as PropTypes from 'prop-types';
2+
3+
export default function chainPropTypes(
4+
propType1: PropTypes.Validator<unknown>,
5+
propType2: PropTypes.Validator<unknown>,
6+
): PropTypes.Validator<unknown> {
7+
if (process.env.NODE_ENV === 'production') {
8+
return () => null;
9+
}
10+
11+
return function validate(...args) {
12+
return propType1(...args) || propType2(...args);
13+
};
14+
}

packages/material-ui-utils/src/deepmerge.js

-24
This file was deleted.

packages/material-ui-utils/src/deepmerge.test.js packages/material-ui-utils/src/deepmerge.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ describe('deepmerge', () => {
77
deepmerge({}, JSON.parse('{ "myProperty": "a", "__proto__" : { "isAdmin" : true } }'), {
88
clone: false,
99
});
10-
expect({}.isAdmin).to.equal(undefined);
10+
11+
expect({}).not.to.have.property('isAdmin');
1112
});
1213

1314
// https://github.com/mui-org/material-ui/issues/20095
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export function isPlainObject(item: unknown): item is Record<keyof any, unknown> {
2+
return item && typeof item === 'object' && item.constructor === Object;
3+
}
4+
5+
export interface DeepmergeOptions {
6+
clone?: boolean;
7+
}
8+
9+
export default function deepmerge<T>(
10+
target: T,
11+
source: unknown,
12+
options: DeepmergeOptions = { clone: true },
13+
): T {
14+
const output = options.clone ? { ...target } : target;
15+
16+
if (isPlainObject(target) && isPlainObject(source)) {
17+
Object.keys(source).forEach((key) => {
18+
// Avoid prototype pollution
19+
if (key === '__proto__') {
20+
return;
21+
}
22+
23+
if (isPlainObject(source[key]) && key in target) {
24+
// Since `output` is a clone of `target` and we have narrowed `target` in this block we can cast to the same type.
25+
(output as Record<keyof any, unknown>)[key] = deepmerge(target[key], source[key], options);
26+
} else {
27+
(output as Record<keyof any, unknown>)[key] = source[key];
28+
}
29+
});
30+
}
31+
32+
return output;
33+
}

packages/material-ui-utils/src/elementAcceptingRef.test.js packages/material-ui-utils/src/elementAcceptingRef.test.tsx

+6-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ReactDOM from 'react-dom';
66
import elementAcceptingRef from './elementAcceptingRef';
77

88
describe('elementAcceptingRef', () => {
9-
function checkPropType(element, required = false) {
9+
function checkPropType(element: any, required = false) {
1010
PropTypes.checkPropTypes(
1111
{ children: required ? elementAcceptingRef.isRequired : elementAcceptingRef },
1212
{ children: element },
@@ -20,11 +20,9 @@ describe('elementAcceptingRef', () => {
2020
});
2121

2222
describe('acceptance when not required', () => {
23-
let rootNode;
24-
25-
function assertPass(element, options = {}) {
26-
const { shouldMount = true } = options;
23+
let rootNode: HTMLElement;
2724

25+
function assertPass(element: any, { shouldMount = true } = {}) {
2826
function testAct() {
2927
checkPropType(element);
3028
if (shouldMount) {
@@ -84,14 +82,14 @@ describe('elementAcceptingRef', () => {
8482
});
8583

8684
it('accepts memo', () => {
87-
const Component = React.memo('div');
85+
const Component = React.memo(React.forwardRef(() => null));
8886

8987
assertPass(<Component />);
9088
});
9189

9290
it('accepts lazy', () => {
9391
const Component = React.lazy(() =>
94-
Promise.resolve({ default: (props) => <div {...props} /> }),
92+
Promise.resolve({ default: (props: any) => <div {...props} /> }),
9593
);
9694

9795
// should actually fail when mounting since the ref is forwarded to a function component
@@ -110,7 +108,7 @@ describe('elementAcceptingRef', () => {
110108
});
111109

112110
describe('rejections', () => {
113-
function assertFail(Component, hint) {
111+
function assertFail(Component: any, hint: string) {
114112
expect(() => {
115113
checkPropType(Component);
116114
}).toErrorDev(

packages/material-ui-utils/src/elementAcceptingRef.js packages/material-ui-utils/src/elementAcceptingRef.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import PropTypes from 'prop-types';
22
import chainPropTypes from './chainPropTypes';
33

4-
function isClassComponent(elementType) {
4+
function isClassComponent(elementType: Function) {
55
// elementType.prototype?.isReactComponent
66
const { prototype = {} } = elementType;
77

88
return Boolean(prototype.isReactComponent);
99
}
1010

11-
function acceptingRef(props, propName, componentName, location, propFullName) {
11+
function acceptingRef(
12+
props: { [key: string]: unknown },
13+
propName: string,
14+
componentName: string,
15+
location: string,
16+
propFullName: string,
17+
) {
1218
const element = props[propName];
1319
const safePropName = propFullName || propName;
1420

@@ -18,7 +24,7 @@ function acceptingRef(props, propName, componentName, location, propFullName) {
1824

1925
let warningHint;
2026

21-
const elementType = element.type;
27+
const elementType: unknown = (element as any).type;
2228
/**
2329
* Blacklisting instead of whitelisting
2430
*
@@ -43,7 +49,10 @@ function acceptingRef(props, propName, componentName, location, propFullName) {
4349
return null;
4450
}
4551

46-
const elementAcceptingRef = chainPropTypes(PropTypes.element, acceptingRef);
52+
const elementAcceptingRef = chainPropTypes(
53+
PropTypes.element,
54+
acceptingRef,
55+
) as PropTypes.Requireable<unknown>;
4756
elementAcceptingRef.isRequired = chainPropTypes(PropTypes.element.isRequired, acceptingRef);
4857

4958
export default elementAcceptingRef;

packages/material-ui-utils/src/elementTypeAcceptingRef.test.js packages/material-ui-utils/src/elementTypeAcceptingRef.test.tsx

+6-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import ReactDOM from 'react-dom';
66
import elementTypeAcceptingRef from './elementTypeAcceptingRef';
77

88
describe('elementTypeAcceptingRef', () => {
9-
function checkPropType(elementType) {
9+
function checkPropType(elementType: any) {
1010
PropTypes.checkPropTypes(
1111
{ component: elementTypeAcceptingRef },
1212
{ component: elementType },
@@ -20,11 +20,9 @@ describe('elementTypeAcceptingRef', () => {
2020
});
2121

2222
describe('acceptance', () => {
23-
let rootNode;
24-
25-
function assertPass(Component, options = {}) {
26-
const { failsOnMount = false, shouldMount = true } = options;
23+
let rootNode: HTMLElement;
2724

25+
function assertPass(Component: any, { failsOnMount = false, shouldMount = true } = {}) {
2826
function testAct() {
2927
checkPropType(Component);
3028
if (shouldMount) {
@@ -88,14 +86,14 @@ describe('elementTypeAcceptingRef', () => {
8886
});
8987

9088
it('accepts memo', () => {
91-
const Component = React.memo('div');
89+
const Component = React.memo(React.forwardRef(() => null));
9290

9391
assertPass(Component);
9492
});
9593

9694
it('accepts lazy', () => {
9795
const Component = React.lazy(() =>
98-
Promise.resolve({ default: (props) => <div {...props} /> }),
96+
Promise.resolve({ default: (props: any) => <div {...props} /> }),
9997
);
10098

10199
// should actually fail when mounting since the ref is forwarded to a function component
@@ -114,7 +112,7 @@ describe('elementTypeAcceptingRef', () => {
114112
});
115113

116114
describe('rejections', () => {
117-
function assertFail(Component, hint) {
115+
function assertFail(Component: any, hint: string) {
118116
expect(() => {
119117
checkPropType(Component);
120118
}).toErrorDev(

packages/material-ui-utils/src/elementTypeAcceptingRef.js packages/material-ui-utils/src/elementTypeAcceptingRef.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
import * as PropTypes from 'prop-types';
1+
import PropTypes from 'prop-types';
22
import chainPropTypes from './chainPropTypes';
33

4-
function isClassComponent(elementType) {
4+
function isClassComponent(elementType: Function) {
55
// elementType.prototype?.isReactComponent
6-
const { prototype = {} } = elementType;
6+
const { prototype } = elementType;
77

88
return Boolean(prototype.isReactComponent);
99
}
1010

11-
function elementTypeAcceptingRef(props, propName, componentName, location, propFullName) {
11+
function elementTypeAcceptingRef(
12+
props: { [key: string]: unknown },
13+
propName: string,
14+
componentName: string,
15+
location: string,
16+
propFullName: string,
17+
) {
1218
const propValue = props[propName];
1319
const safePropName = propFullName || propName;
1420

packages/material-ui-utils/src/exactProp.test.js

-37
This file was deleted.

0 commit comments

Comments
 (0)