Skip to content

Commit

Permalink
[typescript-to-proptypes] Support using Omit on types with conditio…
Browse files Browse the repository at this point in the history
…nal properties (#41033)
  • Loading branch information
flaviendelangle committed Feb 14, 2024
1 parent fb3dfb0 commit c5b4eba
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 29 deletions.
104 changes: 75 additions & 29 deletions packages/typescript-to-proptypes/src/getPropTypesFromFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,48 @@ function getSymbolDocumentation({
return comment !== '' ? comment : undefined;
}

function getType({
project,
symbol,
declaration,
location,
}: {
project: PropTypesProject;
symbol: ts.Symbol;
declaration: ts.Declaration | undefined;
location: ts.Node;
}) {
const symbolType = declaration
? // The proptypes aren't detailed enough that we need all the different combinations
// so we just pick the first and ignore the rest
project.checker.getTypeOfSymbolAtLocation(symbol, declaration)
: project.checker.getTypeOfSymbolAtLocation(symbol, location);

let type: ts.Type;
if (declaration === undefined) {
type = symbolType;
} else {
const declaredType = project.checker.getTypeAtLocation(declaration);
const baseConstraintOfType = project.checker.getBaseConstraintOfType(declaredType);

if (baseConstraintOfType === undefined || baseConstraintOfType === declaredType) {
type = symbolType;
}
// get `React.ElementType` from `C extends React.ElementType`
else if (baseConstraintOfType.aliasSymbol?.escapedName === 'ElementType') {
type = baseConstraintOfType;
} else {
type = symbolType;
}
}

if (!type) {
throw new Error('No types found');
}

return type;
}

function checkType({
type,
location,
Expand Down Expand Up @@ -206,6 +248,38 @@ function checkType({
return createLiteralType({ jsDoc, value: 'null' });
}

if (type.flags & ts.TypeFlags.IndexedAccess) {
const objectType = (type as ts.IndexedAccessType).objectType;

if (objectType.flags & ts.TypeFlags.Conditional) {
const node = createUnionType({
jsDoc,
types: [
(objectType as ts.ConditionalType).resolvedTrueType,
(objectType as ts.ConditionalType).resolvedFalseType,
]
.map((resolveType) => resolveType?.getProperty(name))
.filter((propertySymbol): propertySymbol is ts.Symbol => !!propertySymbol)
.map((propertySymbol) =>
checkType({
type: getType({
project,
symbol: propertySymbol,
declaration: propertySymbol.declarations?.[0],
location,
}),
location,
typeStack,
name,
project,
}),
),
});

return node.types.length === 1 ? node.types[0] : node;
}
}

if (type.getCallSignatures().length) {
return createFunctionType({ jsDoc });
}
Expand Down Expand Up @@ -327,33 +401,7 @@ function checkSymbol({
}
}

const symbolType = declaration
? // The proptypes aren't detailed enough that we need all the different combinations
// so we just pick the first and ignore the rest
project.checker.getTypeOfSymbolAtLocation(symbol, declaration)
: project.checker.getTypeOfSymbolAtLocation(symbol, location);

let type: ts.Type;
if (declaration === undefined) {
type = symbolType;
} else {
const declaredType = project.checker.getTypeAtLocation(declaration);
const baseConstraintOfType = project.checker.getBaseConstraintOfType(declaredType);

if (baseConstraintOfType === undefined || baseConstraintOfType === declaredType) {
type = symbolType;
}
// get `React.ElementType` from `C extends React.ElementType`
else if (baseConstraintOfType.aliasSymbol?.escapedName === 'ElementType') {
type = baseConstraintOfType;
} else {
type = symbolType;
}
}

if (!type) {
throw new Error('No types found');
}
const type = getType({ project, symbol, declaration, location });

// Typechecker only gives the type "any" if it's present in a union
// This means the type of "a" in {a?:any} isn't "any | undefined"
Expand Down Expand Up @@ -574,5 +622,3 @@ interface PropTypesProject extends TypeScriptProject {
shouldInclude: NonNullable<GetPropTypesFromFileOptions['shouldInclude']>;
createPropTypeId: (sigil: ts.Symbol | ts.Type) => number;
}

// project.checker.getTypeAtLocation(property.declarations?.[0]!)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type TextFieldProps<A extends boolean> = A extends true ? { testProp: string } : { testProp: boolean }

type Props<A extends boolean = false> = Omit<TextFieldProps<A>, 'b'>

export function Foo<A extends boolean = false>(props: Props<A>): JSX.Element;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Foo.propTypes = {
testProp: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired,
};

0 comments on commit c5b4eba

Please sign in to comment.