Skip to content

Commit 8b34e26

Browse files
ggascoignedanez
andcommitted
fix: Add support for TSAsExpressions when trying to stringify expressions (#634)
Using a combination of forwardRefs and generics leads to wanting to use type assertions to retain the generic nature of the wrapped class. This plays rather poorly with propTypes. After a lot of mucking around I decided to simply assert the desired type when setting propTypes and defaults. This leads to code like this: ```tsx (MenuItem as ComponentObjectOf<typeof MenuItem>).propTypes - {...} ``` This change extends expressionTo.toArray to deal with the typescript expression. Co-authored-by: Daniel Tschinder <[email protected]> # Conflicts: # src/__tests__/__snapshots__/main-test.js.snap # src/utils/__tests__/expressionTo-test.ts
1 parent 5f6e530 commit 8b34e26

File tree

4 files changed

+137
-0
lines changed

4 files changed

+137
-0
lines changed

src/__tests__/__snapshots__/main-test.js.snap

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,3 +1611,41 @@ Object {
16111611
},
16121612
}
16131613
`;
1614+
1615+
exports[`main fixtures processes component "component_43.tsx" without errors 1`] = `
1616+
Object {
1617+
"description": "",
1618+
"displayName": "MenuItem",
1619+
"methods": Array [],
1620+
"props": Object {
1621+
"children": Object {
1622+
"description": "Menu item contents.",
1623+
"required": false,
1624+
"type": Object {
1625+
"name": "node",
1626+
},
1627+
},
1628+
"classes": Object {
1629+
"description": "Override or extend the styles applied to the component. See CSS API below for more details.",
1630+
"required": false,
1631+
"type": Object {
1632+
"name": "object",
1633+
},
1634+
},
1635+
"component": Object {
1636+
"defaultValue": Object {
1637+
"computed": false,
1638+
"value": "'li'",
1639+
},
1640+
"required": false,
1641+
},
1642+
"disableGutters": Object {
1643+
"defaultValue": Object {
1644+
"computed": false,
1645+
"value": "false",
1646+
},
1647+
"required": false,
1648+
},
1649+
},
1650+
}
1651+
`;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import MatMenuItem, {
4+
MenuItemProps as MatMenuItemProps,
5+
} from '@mui/material/MenuItem';
6+
7+
export type ComponentObjectOf<
8+
T extends React.ElementType,
9+
P = React.ComponentProps<T>
10+
> = React.FunctionComponent<P>;
11+
12+
export type MenuItemProps<
13+
D extends React.ElementType,
14+
P = {}
15+
> = MatMenuItemProps<D, P>;
16+
17+
const MenuItem = React.forwardRef<MenuItemProps<'li'>>(
18+
<C extends React.ElementType>(
19+
props: MenuItemProps<C, { component?: C }>,
20+
ref: React.Ref<any>
21+
) => {
22+
return <MatMenuItem {...props} ref={ref} />;
23+
}
24+
) as unknown as <C extends React.ElementType = 'li'>(
25+
props: MenuItemProps<C, { component?: C }>
26+
) => React.ReactElement;
27+
28+
(MenuItem as ComponentObjectOf<typeof MenuItem>).propTypes = {
29+
/** Menu item contents. */
30+
children: PropTypes.node,
31+
/** Override or extend the styles applied to the component. See CSS API below for more details. */
32+
classes: PropTypes.object,
33+
};
34+
35+
(MenuItem as ComponentObjectOf<typeof MenuItem>).defaultProps = {
36+
component: 'li',
37+
disableGutters: false,
38+
};
39+
40+
export default MenuItem;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { expression, noopImporter } from '../../../tests/utils';
2+
import { Array as expressionToArray } from '../expressionTo';
3+
4+
describe('expressionTo', () => {
5+
describe('MemberExpression', () => {
6+
it('with only identifiers', () => {
7+
expect(
8+
expressionToArray(expression('foo.bar.baz'), noopImporter),
9+
).toEqual(['foo', 'bar', 'baz']);
10+
});
11+
12+
it('with one computed literal', () => {
13+
expect(
14+
expressionToArray(expression('foo["bar"].baz'), noopImporter),
15+
).toEqual(['foo', '"bar"', 'baz']);
16+
});
17+
18+
it('with one computed identifier', () => {
19+
expect(
20+
expressionToArray(expression('foo[bar].baz'), noopImporter),
21+
).toEqual(['foo', 'bar', 'baz']);
22+
});
23+
24+
it('with one computed object', () => {
25+
expect(
26+
expressionToArray(expression('foo[{ a: "true"}].baz'), noopImporter),
27+
).toEqual(['foo', '{a: "true"}', 'baz']);
28+
});
29+
30+
it('with one computed object with spread', () => {
31+
expect(
32+
expressionToArray(expression('foo[{ ...a }].baz'), noopImporter),
33+
).toEqual(['foo', '{...a}', 'baz']);
34+
});
35+
36+
it('with one computed object with method', () => {
37+
expect(
38+
expressionToArray(expression('foo[{ a(){} }].baz'), noopImporter),
39+
).toEqual(['foo', '{a: <function>}', 'baz']);
40+
});
41+
42+
it('with TSAsExpression', () => {
43+
expect(
44+
expressionToArray(
45+
expression('(baz as X).prop', {
46+
filename: 'file.ts',
47+
parserOptions: { plugins: ['typescript'] },
48+
}),
49+
noopImporter,
50+
),
51+
).toEqual(['baz', 'prop']);
52+
});
53+
});
54+
});

src/utils/expressionTo.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ function toArray(path: NodePath): Array<string> {
4242
} else if (t.Identifier.check(node)) {
4343
result.push(node.name);
4444
continue;
45+
} else if (t.TSAsExpression.check(node)) {
46+
if (t.Identifier.check(node.expression)) {
47+
result.push(node.expression.name);
48+
}
49+
continue;
4550
} else if (t.Literal.check(node)) {
4651
result.push(node.raw);
4752
continue;

0 commit comments

Comments
 (0)