Skip to content
This repository was archived by the owner on Sep 21, 2019. It is now read-only.

Support more prop types #19

Merged
merged 1 commit into from
Jan 20, 2018
Merged
Show file tree
Hide file tree
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
185 changes: 135 additions & 50 deletions src/transforms/react-js-make-props-and-state-transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,24 +251,22 @@ function buildInterfaceFromPropTypeObjectLiteral(objectLiteral: ts.ObjectLiteral
// get d() {}, // AccessorDeclaration
// }
.filter(ts.isPropertyAssignment)
.filter(property => {
return (
// Ignore children, React types have it
property.name.getText() !== 'children' &&
ts.isPropertyAccessExpression(property.initializer)
)
})
// Ignore children, React types have it
.filter(property => property.name.getText() !== 'children')
.map(propertyAssignment => {
const name = propertyAssignment.name.getText();
// We have guarantee this in the previous `filter`
const initializer = propertyAssignment.initializer as ts.PropertyAccessExpression
const typeValue = getTypeFromReactPropTypeExpression(initializer);
const isOptional = isPropTypeOptional(initializer);
const initializer = propertyAssignment.initializer;
const isRequired = isPropTypeRequired(initializer);
const typeExpression = isRequired
// We have guaranteed the type in `isPropTypeRequired()`
? (initializer as ts.PropertyAccessExpression).expression
: initializer;
const typeValue = getTypeFromReactPropTypeExpression(typeExpression);

return ts.createPropertySignature(
[],
name,
isOptional ? ts.createToken(ts.SyntaxKind.QuestionToken) : undefined,
isRequired ? undefined : ts.createToken(ts.SyntaxKind.QuestionToken),
typeValue,
undefined,
);
Expand All @@ -282,51 +280,138 @@ function buildInterfaceFromPropTypeObjectLiteral(objectLiteral: ts.ObjectLiteral
*
* @param node React propTypes value
*/
function getTypeFromReactPropTypeExpression(node: ts.PropertyAccessExpression) {
const text = node.getText().replace(/React\.PropTypes\./, '');
function getTypeFromReactPropTypeExpression(node: ts.Expression): ts.TypeNode {
let result = null;
if (/string/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
} else if (/any/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
} else if (/array/.test(text)) {
result = ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
} else if (/bool/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
} else if (/number/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
} else if (/object/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
} else if (/node/.test(text)) {
result = ts.createTypeReferenceNode('React.ReactNode', []);
} else if (/element/.test(text)) {
result = ts.createTypeReferenceNode('JSX.Element', []);
} else if (/func/.test(text)) {
const arrayOfAny = ts.createParameter(
[],
[],
ts.createToken(ts.SyntaxKind.DotDotDotToken),
'args',
undefined,
ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)),
undefined,
);
result = ts.createFunctionTypeNode(
[],
[arrayOfAny],
ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
);
} else {
if (ts.isPropertyAccessExpression(node)) {
/**
* PropTypes.array,
* PropTypes.bool,
* PropTypes.func,
* PropTypes.number,
* PropTypes.object,
* PropTypes.string,
* PropTypes.symbol, (ignore)
* PropTypes.node,
* PropTypes.element,
* PropTypes.any,
*/
const text = node.getText().replace(/React\.PropTypes\./, '');

if (/string/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
} else if (/any/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
} else if (/array/.test(text)) {
result = ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
} else if (/bool/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
} else if (/number/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
} else if (/object/.test(text)) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
} else if (/node/.test(text)) {
result = ts.createTypeReferenceNode('React.ReactNode', []);
} else if (/element/.test(text)) {
result = ts.createTypeReferenceNode('JSX.Element', []);
} else if (/func/.test(text)) {
const arrayOfAny = ts.createParameter(
[],
[],
ts.createToken(ts.SyntaxKind.DotDotDotToken),
'args',
undefined,
ts.createArrayTypeNode(ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)),
undefined,
);
result = ts.createFunctionTypeNode(
[],
[arrayOfAny],
ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
);
}
} else if (ts.isCallExpression(node)) {
/**
* PropTypes.instanceOf(), (ignore)
* PropTypes.oneOf(), // only support oneOf([1, 2]), oneOf(['a', 'b'])
* PropTypes.oneOfType(),
* PropTypes.arrayOf(),
* PropTypes.objectOf(),
* PropTypes.shape(),
*/
const text = node.expression.getText();
if (/oneOf$/.test(text)) {
const argument = node.arguments[0];
if (ts.isArrayLiteralExpression(argument)) {
if (argument.elements.every(elm => ts.isStringLiteral(elm) || ts.isNumericLiteral(elm))) {
result = ts.createUnionTypeNode(
(argument.elements as ts.NodeArray<ts.StringLiteral | ts.NumericLiteral>).map(elm =>
ts.createLiteralTypeNode(elm)
),
)
}
}
} else if (/oneOfType$/.test(text)) {
const argument = node.arguments[0];
if (ts.isArrayLiteralExpression(argument)) {
result = ts.createUnionOrIntersectionTypeNode(
ts.SyntaxKind.UnionType,
argument.elements.map(elm => getTypeFromReactPropTypeExpression(elm)),
);
}
} else if (/arrayOf$/.test(text)) {
const argument = node.arguments[0];
if (argument) {
result = ts.createArrayTypeNode(
getTypeFromReactPropTypeExpression(argument)
)
}
} else if (/objectOf$/.test(text)) {
const argument = node.arguments[0];
if (argument) {
result = ts.createTypeLiteralNode([
ts.createIndexSignature(
undefined,
undefined,
[
ts.createParameter(
undefined,
undefined,
undefined,
'key',
undefined,
ts.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
)
],
getTypeFromReactPropTypeExpression(argument),
)
])
}
} else if (/shape$/.test(text)) {
const argument = node.arguments[0];
if (ts.isObjectLiteralExpression(argument)) {
return buildInterfaceFromPropTypeObjectLiteral(argument)
}
}
}

/**
* customProp,
* anything others
*/
if (result === null) {
result = ts.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword);
}
return result;

return result
}

/**
* Decide if node is optional
* Decide if node is required
* @param node React propTypes member node
*/
function isPropTypeOptional(node: ts.PropertyAccessExpression) {
function isPropTypeRequired(node: ts.Expression) {
if (!ts.isPropertyAccessExpression(node)) return false;

const text = node.getText().replace(/React\.PropTypes\./, '');
return !/\.isRequired/.test(text)
return /\.isRequired/.test(text);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ export default class MyComponent extends React.Component {
string: React.PropTypes.string,
node: React.PropTypes.node,
element: React.PropTypes.element,
oneOf: React.PropTypes.oneOf(['a', 'b', 'c']),
oneOfType: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
]),
arrayOf: React.PropTypes.arrayOf(React.PropTypes.string),
objectOf: React.PropTypes.objectOf(React.PropTypes.string),
shape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number,
}),
anyRequired: React.PropTypes.any.isRequired,
arrayRequired: React.PropTypes.array.isRequired,
boolRequired: React.PropTypes.bool.isRequired,
Expand All @@ -21,6 +32,17 @@ export default class MyComponent extends React.Component {
stringRequired: React.PropTypes.string.isRequired,
nodeRequired: React.PropTypes.node.isRequired,
elementRequired: React.PropTypes.element.isRequired,
oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired,
oneOfTypeRequired: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
]).isRequired,
arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired,
shapeRequired: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number.isRequired,
}).isRequired,
};
render() {
return <div />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ export default class MyComponent extends React.Component<{
string?: string;
node?: React.ReactNode;
element?: JSX.Element;
oneOf?: 'a' | 'b' | 'c';
oneOfType?: string | number;
arrayOf?: string[];
objectOf?: {
[key: string]: string;
};
shape?: {
color?: string;
fontSize?: number;
};
anyRequired: any;
arrayRequired: any[];
boolRequired: boolean;
Expand All @@ -18,6 +28,16 @@ export default class MyComponent extends React.Component<{
stringRequired: string;
nodeRequired: React.ReactNode;
elementRequired: JSX.Element;
oneOfRequired: 'a' | 'b' | 'c';
oneOfTypeRequired: string | number;
arrayOfRequired: string[];
objectOfRequired: {
[key: string]: string;
};
shapeRequired: {
color?: string;
fontSize: number;
};
}, {
}> {
static propTypes = {
Expand All @@ -31,6 +51,17 @@ export default class MyComponent extends React.Component<{
string: React.PropTypes.string,
node: React.PropTypes.node,
element: React.PropTypes.element,
oneOf: React.PropTypes.oneOf(['a', 'b', 'c']),
oneOfType: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
]),
arrayOf: React.PropTypes.arrayOf(React.PropTypes.string),
objectOf: React.PropTypes.objectOf(React.PropTypes.string),
shape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number,
}),
anyRequired: React.PropTypes.any.isRequired,
arrayRequired: React.PropTypes.array.isRequired,
boolRequired: React.PropTypes.bool.isRequired,
Expand All @@ -40,6 +71,17 @@ export default class MyComponent extends React.Component<{
stringRequired: React.PropTypes.string.isRequired,
nodeRequired: React.PropTypes.node.isRequired,
elementRequired: React.PropTypes.element.isRequired,
oneOfRequired: React.PropTypes.oneOf(['a', 'b', 'c']).isRequired,
oneOfTypeRequired: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
]).isRequired,
arrayOfRequired: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
objectOfRequired: React.PropTypes.objectOf(React.PropTypes.string).isRequired,
shapeRequired: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number.isRequired,
}).isRequired,
};
render() {
return <div />;
Expand Down