mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Flatten built-in utility types in the API snapshot (#52280)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52280 Changelog: [Internal] Adds a type-simplifyng transform for the API snapshot, with the goal of resolving some built-in TS types during build time. Most notably, it's able to simplify `Omit` structures emitted by the `flow-api-translator` when translating Flow's type spread operator. It builds upon a simplified type inlining transform from the previous approach. The type inlining transform is able to handle inlining type references and resolution of built-in TS types on literal types: - `Omit` - `Readonly` - `Partial` - `keyof` Reference inlining is performed top-down and built-in type resolution is performed bottom-up, which makes it possible for the second step to assume working on type literals. Type simplifying transform uses the type inlining to reduce type references encountered inside `Omits` to their literal shapes, which makes possible to determine whether `Omit` is neccessary case-by-case. If `Omit` is redundant, it can be safely removed. If it's not, the omitted keys can be reduced to represent a subset of keys existing in the target type. It also keeps the ability to resolve `Partial` and `Readonly` types on type literals, simplifying the snapshot further. An example diff the transform can handle: Before: ``` export declare type AccessibilityProps = Readonly< Omit< AccessibilityPropsAndroid, | keyof { accessibilityActions?: ReadonlyArray<AccessibilityActionInfo> accessibilityHint?: string accessibilityLabel?: string accessibilityRole?: AccessibilityRole accessibilityState?: AccessibilityState accessibilityValue?: AccessibilityValue accessible?: boolean "aria-busy"?: boolean "aria-checked"?: "mixed" | (boolean | undefined) "aria-disabled"?: boolean "aria-expanded"?: boolean "aria-hidden"?: boolean "aria-label"?: string "aria-selected"?: boolean "aria-valuemax"?: AccessibilityValue["max"] "aria-valuemin"?: AccessibilityValue["min"] "aria-valuenow"?: AccessibilityValue["now"] "aria-valuetext"?: AccessibilityValue["text"] role?: Role } | keyof AccessibilityPropsIOS > & Omit< AccessibilityPropsIOS, keyof { accessibilityActions?: ReadonlyArray<AccessibilityActionInfo> accessibilityHint?: string accessibilityLabel?: string accessibilityRole?: AccessibilityRole accessibilityState?: AccessibilityState accessibilityValue?: AccessibilityValue accessible?: boolean "aria-busy"?: boolean "aria-checked"?: "mixed" | (boolean | undefined) "aria-disabled"?: boolean "aria-expanded"?: boolean "aria-hidden"?: boolean "aria-label"?: string "aria-selected"?: boolean "aria-valuemax"?: AccessibilityValue["max"] "aria-valuemin"?: AccessibilityValue["min"] "aria-valuenow"?: AccessibilityValue["now"] "aria-valuetext"?: AccessibilityValue["text"] role?: Role } > & { accessibilityActions?: ReadonlyArray<AccessibilityActionInfo> accessibilityHint?: string accessibilityLabel?: string accessibilityRole?: AccessibilityRole accessibilityState?: AccessibilityState accessibilityValue?: AccessibilityValue accessible?: boolean "aria-busy"?: boolean "aria-checked"?: "mixed" | (boolean | undefined) "aria-disabled"?: boolean "aria-expanded"?: boolean "aria-hidden"?: boolean "aria-label"?: string "aria-selected"?: boolean "aria-valuemax"?: AccessibilityValue["max"] "aria-valuemin"?: AccessibilityValue["min"] "aria-valuenow"?: AccessibilityValue["now"] "aria-valuetext"?: AccessibilityValue["text"] role?: Role } > ``` After: ``` export declare type AccessibilityProps = Readonly< AccessibilityPropsAndroid & AccessibilityPropsIOS & { accessibilityActions?: ReadonlyArray<AccessibilityActionInfo> accessibilityHint?: string accessibilityLabel?: string accessibilityRole?: AccessibilityRole accessibilityState?: AccessibilityState accessibilityValue?: AccessibilityValue accessible?: boolean "aria-busy"?: boolean "aria-checked"?: "mixed" | (boolean | undefined) "aria-disabled"?: boolean "aria-expanded"?: boolean "aria-hidden"?: boolean "aria-label"?: string "aria-selected"?: boolean "aria-valuemax"?: AccessibilityValue["max"] "aria-valuemin"?: AccessibilityValue["min"] "aria-valuenow"?: AccessibilityValue["now"] "aria-valuetext"?: AccessibilityValue["text"] role?: Role } > ``` Reviewed By: huntie Differential Revision: D77295302 fbshipit-source-id: 213aef46035bde4f9783353b5344a6986a418399
This commit is contained in:
committed by
Facebook GitHub Bot
parent
c6608685cb
commit
df5cd55cdb
@@ -36,6 +36,7 @@ const inputFilesPostTransforms: $ReadOnlyArray<PluginObj<mixed>> = [
|
||||
];
|
||||
|
||||
const postTransforms: $ReadOnlyArray<PluginObj<mixed>> = [
|
||||
require('./transforms/typescript/simplifyTypes'),
|
||||
require('./transforms/typescript/sortProperties'),
|
||||
require('./transforms/typescript/sortUnions'),
|
||||
require('./transforms/typescript/removeUndefinedFromOptionalMembers'),
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
const babel = require('@babel/core');
|
||||
|
||||
const postTransforms = [require('../simplifyTypes/inlineTypesVisitor')];
|
||||
|
||||
async function applyPostTransforms(inSrc: string): Promise<string> {
|
||||
const result = await babel.transformAsync(inSrc, {
|
||||
plugins: ['@babel/plugin-syntax-typescript', ...postTransforms],
|
||||
});
|
||||
|
||||
return result.code;
|
||||
}
|
||||
|
||||
describe('inlineTypesVisitor', () => {
|
||||
test('should inline type non-generic type aliases', async () => {
|
||||
const code = `
|
||||
type AnimatedNodeConfig = {
|
||||
readonly debugID?: string | undefined;
|
||||
};
|
||||
|
||||
export type Example = Omit<
|
||||
AnimatedNodeConfig,
|
||||
keyof {
|
||||
useNativeDriver: boolean;
|
||||
}
|
||||
> & {
|
||||
useNativeDriver: boolean;
|
||||
};
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type AnimatedNodeConfig = {
|
||||
readonly debugID?: string | undefined;
|
||||
};
|
||||
export type Example = {
|
||||
readonly debugID?: string | undefined;
|
||||
useNativeDriver: boolean;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should skip recursive definitions', async () => {
|
||||
const code = `
|
||||
export type LinkedNode = {
|
||||
value: number;
|
||||
next: LinkedNode;
|
||||
};
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"export type LinkedNode = {
|
||||
value: number;
|
||||
next: LinkedNode;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should skip co-recursive definitions', async () => {
|
||||
const code = `
|
||||
type Expr = Add | Multiply | number;
|
||||
|
||||
export type Add = {
|
||||
type: 'add';
|
||||
lhs: Expr;
|
||||
rhs: Expr;
|
||||
};
|
||||
|
||||
export type Multiply = {
|
||||
type: 'multiply';
|
||||
lhs: Expr;
|
||||
rhs: Expr;
|
||||
};
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Expr = Add | Multiply | number;
|
||||
export type Add = {
|
||||
type: 'add';
|
||||
lhs: Add | Multiply | number;
|
||||
rhs: Expr;
|
||||
};
|
||||
export type Multiply = {
|
||||
type: 'multiply';
|
||||
lhs: Expr;
|
||||
rhs: Expr;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('keyof {} is never', async () => {
|
||||
const code = `
|
||||
export type NeverInDisguise = keyof {};
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"export type NeverInDisguise = never;"`,
|
||||
);
|
||||
});
|
||||
|
||||
test('keyof for objects with computed string properties', async () => {
|
||||
const code = `
|
||||
export type Foo = keyof { 'a-key': number, 'b-key': number };
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"export type Foo = \\"a-key\\" | \\"b-key\\";"`,
|
||||
);
|
||||
});
|
||||
|
||||
test('resolves Readonly type', async () => {
|
||||
const code = `
|
||||
export type Foo = Readonly<{ a?: number, 'b-key': number }>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"export type Foo = {
|
||||
readonly a?: number;
|
||||
readonly 'b-key': number;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('resolves Partial type', async () => {
|
||||
const code = `
|
||||
export type Foo = Partial<{ a: number, 'b-key': number }>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"export type Foo = {
|
||||
a?: number;
|
||||
'b-key'?: number;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('resolves simple intersection', async () => {
|
||||
const code = `
|
||||
export type Foo = { a?: number, 'b-key': number } & { c: number, 'd-key'?: number };
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"export type Foo = {
|
||||
a?: number;
|
||||
\\"b-key\\": number;
|
||||
c: number;
|
||||
\\"d-key\\"?: number;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('can inline multiple references to a single type in a declaration', async () => {
|
||||
const code = `
|
||||
export declare type Result =
|
||||
& Omit<{ alpha: 1; }, keyof Beta>
|
||||
& Omit<Beta, "gamma">
|
||||
;
|
||||
|
||||
declare type Beta = Readonly<{
|
||||
beta: 2;
|
||||
}>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"export declare type Result = {
|
||||
alpha: 1;
|
||||
readonly beta: 2;
|
||||
};
|
||||
declare type Beta = {
|
||||
readonly beta: 2;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should preserve dec comments when inlining types', async () => {
|
||||
const code = `
|
||||
declare type A = {
|
||||
/**
|
||||
* Comment for prop1 in A
|
||||
*/
|
||||
prop1: string;
|
||||
/**
|
||||
* Comment for prop2 in A
|
||||
*/
|
||||
prop2: number;
|
||||
};
|
||||
|
||||
declare type B = {
|
||||
/**
|
||||
* Comment for prop1 in B
|
||||
*/
|
||||
prop1: string[];
|
||||
/**
|
||||
* Comment for prop3 in B
|
||||
*/
|
||||
prop3: boolean;
|
||||
};
|
||||
|
||||
declare type C = {
|
||||
/**
|
||||
* Comment for prop4 in D
|
||||
*/
|
||||
prop4: number;
|
||||
};
|
||||
|
||||
export declare type D = Omit<Omit<A, keyof B> & B, keyof C> & C;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"declare type A = {
|
||||
/**
|
||||
* Comment for prop1 in A
|
||||
*/
|
||||
prop1: string;
|
||||
/**
|
||||
* Comment for prop2 in A
|
||||
*/
|
||||
prop2: number;
|
||||
};
|
||||
declare type B = {
|
||||
/**
|
||||
* Comment for prop1 in B
|
||||
*/
|
||||
prop1: string[];
|
||||
/**
|
||||
* Comment for prop3 in B
|
||||
*/
|
||||
prop3: boolean;
|
||||
};
|
||||
declare type C = {
|
||||
/**
|
||||
* Comment for prop4 in D
|
||||
*/
|
||||
prop4: number;
|
||||
};
|
||||
export declare type D = {
|
||||
/**
|
||||
* Comment for prop2 in A
|
||||
*/
|
||||
prop2: number;
|
||||
/**
|
||||
* Comment for prop1 in B
|
||||
*/
|
||||
prop1: string[];
|
||||
/**
|
||||
* Comment for prop3 in B
|
||||
*/
|
||||
prop3: boolean;
|
||||
/**
|
||||
* Comment for prop4 in D
|
||||
*/
|
||||
prop4: number;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should inline generic type aliases', async () => {
|
||||
const code = `
|
||||
type Required<T, U = string> = {
|
||||
data: T[];
|
||||
} & {
|
||||
title: U;
|
||||
}
|
||||
|
||||
type Optional<T> = {
|
||||
header: T;
|
||||
}
|
||||
|
||||
export type Example<DataT> = Omit<
|
||||
Required<DataT>,
|
||||
keyof Optional<DataT>
|
||||
> & Optional<DataT>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Required<T, U = string> = {
|
||||
data: T[];
|
||||
title: U;
|
||||
};
|
||||
type Optional<T> = {
|
||||
header: T;
|
||||
};
|
||||
export type Example<DataT> = {
|
||||
data: DataT[];
|
||||
title: string;
|
||||
header: DataT;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should inline generic type equal to one of its type parameters', async () => {
|
||||
const code = `
|
||||
type PropsAlias<Props extends {}> = Props;
|
||||
export type Example = PropsAlias<{a: string}>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type PropsAlias<Props extends {}> = Props;
|
||||
export type Example = {
|
||||
a: string;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should not confuse local symbols with global ones', async () => {
|
||||
const code = `
|
||||
type PropsAlias<Props extends {}> = Props;
|
||||
|
||||
type Props = {
|
||||
b: number;
|
||||
};
|
||||
|
||||
export type Example = PropsAlias<{a: string}>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type PropsAlias<Props extends {}> = Props;
|
||||
type Props = {
|
||||
b: number;
|
||||
};
|
||||
export type Example = {
|
||||
a: string;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should inline types inside Omit when used in union', async () => {
|
||||
const code = `
|
||||
declare type ProgressBarAndroidBaseProps = {
|
||||
readonly animating?: boolean | undefined;
|
||||
readonly color?: ColorValue | undefined;
|
||||
readonly testID?: string | undefined;
|
||||
};
|
||||
|
||||
declare type DeterminateProgressBarAndroidStyleAttrProp = {
|
||||
styleAttr: "Horizontal";
|
||||
indeterminate: false;
|
||||
progress: number;
|
||||
};
|
||||
|
||||
declare type IndeterminateProgressBarAndroidStyleAttrProp = {
|
||||
styleAttr: "Normal"
|
||||
indeterminate: true;
|
||||
};
|
||||
|
||||
export declare type ProgressBarAndroidProps =
|
||||
| Readonly<
|
||||
Omit<
|
||||
ProgressBarAndroidBaseProps,
|
||||
"styleAttr" | "indeterminate" | "progress"
|
||||
> &
|
||||
Omit<DeterminateProgressBarAndroidStyleAttrProp, never> & {}
|
||||
>
|
||||
| Readonly<
|
||||
Omit<
|
||||
ProgressBarAndroidBaseProps,
|
||||
"styleAttr" | "indeterminate"
|
||||
> &
|
||||
Omit<IndeterminateProgressBarAndroidStyleAttrProp, never> & {}
|
||||
>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"declare type ProgressBarAndroidBaseProps = {
|
||||
readonly animating?: boolean | undefined;
|
||||
readonly color?: ColorValue | undefined;
|
||||
readonly testID?: string | undefined;
|
||||
};
|
||||
declare type DeterminateProgressBarAndroidStyleAttrProp = {
|
||||
styleAttr: \\"Horizontal\\";
|
||||
indeterminate: false;
|
||||
progress: number;
|
||||
};
|
||||
declare type IndeterminateProgressBarAndroidStyleAttrProp = {
|
||||
styleAttr: \\"Normal\\";
|
||||
indeterminate: true;
|
||||
};
|
||||
export declare type ProgressBarAndroidProps = {
|
||||
readonly animating?: boolean | undefined;
|
||||
readonly color?: ColorValue | undefined;
|
||||
readonly testID?: string | undefined;
|
||||
readonly styleAttr: \\"Horizontal\\";
|
||||
readonly indeterminate: false;
|
||||
readonly progress: number;
|
||||
} | {
|
||||
readonly animating?: boolean | undefined;
|
||||
readonly color?: ColorValue | undefined;
|
||||
readonly testID?: string | undefined;
|
||||
readonly styleAttr: \\"Normal\\";
|
||||
readonly indeterminate: true;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
const babel = require('@babel/core');
|
||||
|
||||
const postTransforms = [require('../simplifyTypes')];
|
||||
|
||||
async function applyPostTransforms(inSrc: string): Promise<string> {
|
||||
const result = await babel.transformAsync(inSrc, {
|
||||
plugins: ['@babel/plugin-syntax-typescript', ...postTransforms],
|
||||
});
|
||||
|
||||
return result.code;
|
||||
}
|
||||
|
||||
describe('simplifyTypes', () => {
|
||||
test('should resolve Readonly on a type literal', async () => {
|
||||
const code = `
|
||||
type Baz = Readonly<{
|
||||
foo: string;
|
||||
}>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Baz = {
|
||||
readonly foo: string;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should keep Readonly on a type reference', async () => {
|
||||
const code = `
|
||||
type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
|
||||
type Baz = Readonly<Foo>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
type Baz = Readonly<Foo>;"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve Partial on a type literal', async () => {
|
||||
const code = `
|
||||
type Baz = Partial<{
|
||||
foo: string;
|
||||
}>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Baz = {
|
||||
foo?: string;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should keep Partial on a type reference', async () => {
|
||||
const code = `
|
||||
type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
|
||||
type Baz = Partial<Foo>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
type Baz = Partial<Foo>;"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve nested utility types on a type literal', async () => {
|
||||
const code = `
|
||||
type Baz = Readonly<Partial<{
|
||||
foo: string;
|
||||
}>>;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Baz = {
|
||||
readonly foo?: string;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve intersection on type literals', async () => {
|
||||
const code = `
|
||||
type Baz = {
|
||||
foo: string;
|
||||
} & {
|
||||
bar: number;
|
||||
};
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Baz = {
|
||||
foo: string;
|
||||
bar: number;
|
||||
};"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve intersection with an empty type', async () => {
|
||||
const code = `
|
||||
type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
|
||||
type Baz = Foo & {};
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
type Baz = Foo;"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve a simple Omit', async () => {
|
||||
const code = `
|
||||
type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
|
||||
type Baz = (arg: Omit<Foo, 'bar'>) => void;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
type Baz = (arg: Foo) => void;"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve Omit with keyof', async () => {
|
||||
const code = `
|
||||
type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
|
||||
type Bar = {
|
||||
bar: number;
|
||||
};
|
||||
|
||||
type Baz = (arg: Omit<Foo, keyof Bar>) => void;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
type Bar = {
|
||||
bar: number;
|
||||
};
|
||||
type Baz = (arg: Foo) => void;"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve Omit with keyof {}', async () => {
|
||||
const code = `
|
||||
type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
|
||||
type baz = Omit<Foo, keyof {}>
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Foo = {
|
||||
foo: string;
|
||||
};
|
||||
type baz = Foo;"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve Omit with keyof when types overlap', async () => {
|
||||
const code = `
|
||||
type Foo = {
|
||||
foo: string;
|
||||
bar: number;
|
||||
};
|
||||
|
||||
type Baz = (arg: Omit<Foo, 'bar' | 'baz'>) => void;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Foo = {
|
||||
foo: string;
|
||||
bar: number;
|
||||
};
|
||||
type Baz = (arg: Omit<Foo, \\"bar\\">) => void;"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve Omit when types overlap', async () => {
|
||||
const code = `
|
||||
type Foo = {
|
||||
foo: string;
|
||||
bar: number;
|
||||
};
|
||||
|
||||
type Bar = {
|
||||
bar: number;
|
||||
baz: string;
|
||||
};
|
||||
|
||||
type Baz = (arg: Omit<Foo, keyof Bar>) => void;
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
"type Foo = {
|
||||
foo: string;
|
||||
bar: number;
|
||||
};
|
||||
type Bar = {
|
||||
bar: number;
|
||||
baz: string;
|
||||
};
|
||||
type Baz = (arg: Omit<Foo, \\"bar\\">) => void;"
|
||||
`);
|
||||
});
|
||||
|
||||
test('should resolve Omit keys when the object type is unresolvable', async () => {
|
||||
const code = `
|
||||
type Foo<T> = Omit<Bar, keyof { touchHistory: string }>
|
||||
`;
|
||||
|
||||
const result = await applyPostTransforms(code);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"type Foo<T> = Omit<Bar, \\"touchHistory\\">;"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import traverse from '@babel/traverse';
|
||||
|
||||
const t = require('@babel/types');
|
||||
|
||||
export default function alignTypeParameters(
|
||||
node: BabelNodeTSType,
|
||||
declarationPath: NodePath<t.TSTypeAliasDeclaration>,
|
||||
path: NodePath<t.Node>,
|
||||
): BabelNodeTSType {
|
||||
const declarationTypeParameters = declarationPath.node.typeParameters?.params;
|
||||
const nodeTypeParameters = path.node.typeParameters?.params;
|
||||
|
||||
if (!declarationTypeParameters || !nodeTypeParameters) {
|
||||
throw new Error(
|
||||
`No type parameters found for ${declarationPath.node.id.name ?? ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (nodeTypeParameters.length > declarationTypeParameters.length) {
|
||||
throw new Error(
|
||||
`Encountered ${nodeTypeParameters.length} type parameters for type ${declarationPath.node.id.name ?? ''} while at maximum ${declarationTypeParameters.length} are allowed`,
|
||||
);
|
||||
}
|
||||
|
||||
const genericMapping = new Map<string, BabelNodeTSType>();
|
||||
for (let i = 0; i < declarationTypeParameters.length; i++) {
|
||||
const declarationTypeParameter = declarationTypeParameters[i];
|
||||
let nodeTypeParameter: ?BabelNodeTSType = nodeTypeParameters[
|
||||
i
|
||||
] as $FlowFixMe;
|
||||
|
||||
if (nodeTypeParameter == null) {
|
||||
nodeTypeParameter = declarationTypeParameter.default;
|
||||
}
|
||||
|
||||
if (nodeTypeParameter == null) {
|
||||
throw new Error(
|
||||
`No value provided for a required type parameter ${declarationPath.node.id.name ?? ''}`,
|
||||
);
|
||||
}
|
||||
|
||||
genericMapping.set(declarationTypeParameter.name, nodeTypeParameter);
|
||||
}
|
||||
|
||||
// handle edge case where the generic type is equal to one of the type
|
||||
// parameters, i.e. `type Foo<T> = T`
|
||||
if (t.isTSTypeReference(node) && t.isIdentifier(node.typeName)) {
|
||||
const mappedType = genericMapping.get(node.typeName.name);
|
||||
if (mappedType) {
|
||||
return mappedType;
|
||||
}
|
||||
}
|
||||
|
||||
const wrapped = t.tsTypeAliasDeclaration(
|
||||
t.identifier('Wrapper'),
|
||||
undefined,
|
||||
node,
|
||||
);
|
||||
|
||||
traverse(t.file(t.program([wrapped])), {
|
||||
TSTypeReference(innerPath) {
|
||||
const type = innerPath.node.typeName;
|
||||
if (!t.isIdentifier(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mappedType = t.cloneDeep(genericMapping.get(type.name));
|
||||
if (!mappedType) {
|
||||
return;
|
||||
}
|
||||
|
||||
innerPath.replaceWith(mappedType);
|
||||
innerPath.skip();
|
||||
},
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {InlineVisitorState} from './visitorState';
|
||||
|
||||
type KeyofLayer = {
|
||||
type: 'keyof',
|
||||
};
|
||||
|
||||
type OmitLayer = {
|
||||
type: 'omit',
|
||||
};
|
||||
|
||||
type UnionLayer = {
|
||||
type: 'union',
|
||||
};
|
||||
|
||||
type ExtendsLayer = {
|
||||
type: 'extends',
|
||||
};
|
||||
|
||||
type ArrayLayer = {
|
||||
type: 'array',
|
||||
};
|
||||
|
||||
type TypeAliasLayer = {
|
||||
type: 'typeAlias',
|
||||
typeParams?: string[],
|
||||
};
|
||||
|
||||
type TypeParameterInstantiationLayer = {
|
||||
type: 'typeParameterInstantiation',
|
||||
};
|
||||
|
||||
type UnresolvableTypeLayer = {
|
||||
type: 'unresolvableType',
|
||||
};
|
||||
|
||||
type ResolvableTypeLayer = {
|
||||
type: 'resolvableType',
|
||||
};
|
||||
|
||||
export type StackLayer =
|
||||
| KeyofLayer
|
||||
| OmitLayer
|
||||
| UnionLayer
|
||||
| ExtendsLayer
|
||||
| ArrayLayer
|
||||
| TypeAliasLayer
|
||||
| TypeParameterInstantiationLayer
|
||||
| UnresolvableTypeLayer
|
||||
| ResolvableTypeLayer;
|
||||
|
||||
export function pushLayer(state: InlineVisitorState, layer: StackLayer) {
|
||||
state.stack.push(layer);
|
||||
}
|
||||
|
||||
export function popLayer(state: InlineVisitorState, type: StackLayer['type']) {
|
||||
const top = state.stack[state.stack.length - 1];
|
||||
if (!top || top.type !== type) {
|
||||
throw new Error(
|
||||
`Unexpected stack state. Expected ${type}, got ${top.type}`,
|
||||
);
|
||||
}
|
||||
state.stack.pop();
|
||||
}
|
||||
|
||||
export function isDefiningType(
|
||||
state: InlineVisitorState,
|
||||
alias: string,
|
||||
): boolean {
|
||||
return state.parentTypeAliases?.has(alias) ?? false;
|
||||
}
|
||||
|
||||
export function insideKeyofLayer(state: InlineVisitorState): boolean {
|
||||
return state.stack.some(layer => layer.type === 'keyof');
|
||||
}
|
||||
|
||||
export function insideOmitLayer(state: InlineVisitorState): boolean {
|
||||
return state.stack.some(layer => layer.type === 'omit');
|
||||
}
|
||||
|
||||
export function insideUnionLayer(state: InlineVisitorState): boolean {
|
||||
return state.stack.some(layer => layer.type === 'union');
|
||||
}
|
||||
|
||||
export function insideExtendsLayer(state: InlineVisitorState): boolean {
|
||||
return state.stack.some(layer => layer.type === 'extends');
|
||||
}
|
||||
|
||||
export function insideArrayLayer(state: InlineVisitorState): boolean {
|
||||
return state.stack.some(layer => layer.type === 'array');
|
||||
}
|
||||
|
||||
export function insideTypeAliasLayerWithTypeParam(
|
||||
state: InlineVisitorState,
|
||||
parameter: string,
|
||||
): boolean {
|
||||
return state.stack.some(
|
||||
layer =>
|
||||
layer.type === 'typeAlias' &&
|
||||
layer.typeParams?.includes(parameter) === true,
|
||||
);
|
||||
}
|
||||
|
||||
export function insideIndexedAccessLayer(state: InlineVisitorState): boolean {
|
||||
return state.stack.some(layer => layer.type === 'indexedAccess');
|
||||
}
|
||||
|
||||
export function insideUnresolvableTypeInstantiation(
|
||||
state: InlineVisitorState,
|
||||
): boolean {
|
||||
const lastUnresolvableTypeIdx = state.stack.findLastIndex(
|
||||
layer => layer.type === 'unresolvableType',
|
||||
);
|
||||
const lastResolvableTypeIdx = state.stack.findLastIndex(
|
||||
layer => layer.type === 'resolvableType',
|
||||
);
|
||||
const lastTypeParameterInstantiationIdx = state.stack.findLastIndex(
|
||||
layer => layer.type === 'typeParameterInstantiation',
|
||||
);
|
||||
|
||||
return (
|
||||
lastUnresolvableTypeIdx >= 0 &&
|
||||
lastTypeParameterInstantiationIdx >= 0 &&
|
||||
lastResolvableTypeIdx < lastUnresolvableTypeIdx
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {NodePath, Visitor} from '@babel/traverse';
|
||||
|
||||
const t = require('@babel/types');
|
||||
|
||||
export type GatherTypeAliasesVisitorState = {
|
||||
+aliasToPathMap: Map<string, NodePath<t.TSTypeAliasDeclaration>>,
|
||||
};
|
||||
|
||||
/**
|
||||
* Gather all type aliases in the file into a map
|
||||
*/
|
||||
const gatherTypeAliasesVisitor: Visitor<GatherTypeAliasesVisitorState> = {
|
||||
TSTypeAliasDeclaration(path, state) {
|
||||
const alias = path.node.id.name;
|
||||
state.aliasToPathMap.set(alias, path);
|
||||
},
|
||||
};
|
||||
|
||||
export default gatherTypeAliasesVisitor;
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {BaseVisitorState} from './visitorState';
|
||||
import type {PluginObj} from '@babel/core';
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import gatherTypeAliasesVisitor from './gatherTypeAliasesVisitor';
|
||||
import {canResolveBuiltinType, resolveBuiltinType} from './resolveBuiltinType';
|
||||
import {resolveIntersection} from './resolveIntersection';
|
||||
import {resolveTSType} from './resolveTSType';
|
||||
|
||||
const t = require('@babel/types');
|
||||
|
||||
const mergeObjects: PluginObj<BaseVisitorState> = {
|
||||
visitor: {
|
||||
Program: {
|
||||
enter(path, state): void {
|
||||
state.aliasToPathMap = new Map<
|
||||
string,
|
||||
NodePath<t.TSTypeAliasDeclaration>,
|
||||
>();
|
||||
state.nodeToAliasMap = new Map<t.Node, string>();
|
||||
state.parentTypeAliases = new Set<string>();
|
||||
|
||||
path.traverse(gatherTypeAliasesVisitor, {
|
||||
aliasToPathMap: state.aliasToPathMap,
|
||||
});
|
||||
},
|
||||
|
||||
exit(path, state): void {},
|
||||
},
|
||||
TSIntersectionType: {
|
||||
enter() {
|
||||
// Do nothing
|
||||
},
|
||||
exit(path, state) {
|
||||
resolveIntersection(path, state);
|
||||
},
|
||||
},
|
||||
TSTypeReference: {
|
||||
enter(path, state): void {},
|
||||
// Builtin-type resolution is done bottom-up
|
||||
exit(path, state) {
|
||||
if (!t.isIdentifier(path.node.typeName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeName = path.node.typeName.name;
|
||||
if (canResolveBuiltinType(typeName)) {
|
||||
resolveBuiltinType(path, state, resolveTSType);
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Visitor state is only used internally, so we can safely cast to PluginObj<mixed>.
|
||||
module.exports = mergeObjects as $FlowFixMe as PluginObj<mixed>;
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {InlineVisitorState} from './visitorState';
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import alignTypeParameters from './alignTypeParameters';
|
||||
|
||||
const t = require('@babel/types');
|
||||
const debug = require('debug')('build-types:transforms:inlineTypes');
|
||||
|
||||
export default function inlineType(
|
||||
state: InlineVisitorState,
|
||||
path: NodePath<t.Node>,
|
||||
alias: string,
|
||||
): void {
|
||||
const declarationPath = state.aliasToPathMap?.get(alias);
|
||||
|
||||
if (!declarationPath) {
|
||||
debug(`No declaration found for ${alias ?? ''}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let cloned = t.cloneDeep(declarationPath.node.typeAnnotation);
|
||||
|
||||
// If the inlined type is generic, align the provided type parameters
|
||||
// to the declaration
|
||||
if (declarationPath.node.typeParameters) {
|
||||
cloned = alignTypeParameters(cloned, declarationPath, path);
|
||||
}
|
||||
|
||||
state.parentTypeAliases?.add(alias);
|
||||
state.nodeToAliasMap?.set(cloned, alias);
|
||||
path.replaceWith(cloned);
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {InlineVisitorState} from './visitorState';
|
||||
import type {PluginObj} from '@babel/core';
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import {
|
||||
insideTypeAliasLayerWithTypeParam,
|
||||
isDefiningType,
|
||||
popLayer,
|
||||
pushLayer,
|
||||
} from './contextStack';
|
||||
import gatherTypeAliasesVisitor from './gatherTypeAliasesVisitor';
|
||||
import inlineType from './inlineType';
|
||||
import {canResolveBuiltinType, resolveBuiltinType} from './resolveBuiltinType';
|
||||
import {resolveIntersection} from './resolveIntersection';
|
||||
import {resolveTypeOperator} from './resolveTypeOperator';
|
||||
import {onNodeExit, shouldSkipInliningType} from './utils';
|
||||
|
||||
const t = require('@babel/types');
|
||||
|
||||
const inlineTypes: PluginObj<InlineVisitorState> = {
|
||||
visitor: {
|
||||
Program: {
|
||||
enter(path, state): void {
|
||||
if (!state.aliasToPathMap) {
|
||||
state.aliasToPathMap = new Map<
|
||||
string,
|
||||
NodePath<t.TSTypeAliasDeclaration>,
|
||||
>();
|
||||
|
||||
path.traverse(gatherTypeAliasesVisitor, {
|
||||
aliasToPathMap: state.aliasToPathMap,
|
||||
});
|
||||
}
|
||||
|
||||
state.parentTypeAliases = new Set<string>();
|
||||
state.nodeToAliasMap = new Map<t.Node, string>();
|
||||
state.stack = [];
|
||||
},
|
||||
exit(path, state): void {
|
||||
// Do nothing
|
||||
},
|
||||
},
|
||||
TSTypeLiteral: {
|
||||
enter() {
|
||||
// Do nothing
|
||||
},
|
||||
exit(path, state) {
|
||||
onNodeExit(state, path.node);
|
||||
},
|
||||
},
|
||||
TSFunctionType: {
|
||||
enter() {
|
||||
// Do nothing
|
||||
},
|
||||
exit(path, state) {
|
||||
onNodeExit(state, path.node);
|
||||
},
|
||||
},
|
||||
TSTypeOperator: {
|
||||
enter(path, state) {
|
||||
if (path.node.operator === 'keyof') {
|
||||
pushLayer(state, {type: 'keyof'});
|
||||
}
|
||||
},
|
||||
exit(path, state) {
|
||||
if (path.node.operator === 'keyof') {
|
||||
popLayer(state, 'keyof');
|
||||
}
|
||||
resolveTypeOperator(path, state);
|
||||
},
|
||||
},
|
||||
TSTypeParameter: {
|
||||
enter(path, state) {
|
||||
if (path.node.constraint) {
|
||||
pushLayer(state, {type: 'extends'});
|
||||
}
|
||||
},
|
||||
exit(path, state) {
|
||||
if (path.node.constraint) {
|
||||
popLayer(state, 'extends');
|
||||
}
|
||||
},
|
||||
},
|
||||
TSTypeParameterInstantiation: {
|
||||
enter(path, state) {
|
||||
pushLayer(state, {type: 'typeParameterInstantiation'});
|
||||
},
|
||||
exit(path, state) {
|
||||
popLayer(state, 'typeParameterInstantiation');
|
||||
},
|
||||
},
|
||||
TSTypeAliasDeclaration: {
|
||||
enter(path, state): void {
|
||||
state.parentTypeAliases?.add(path.node.id.name);
|
||||
pushLayer(state, {
|
||||
type: 'typeAlias',
|
||||
typeParams: path.node.typeParameters?.params?.map(
|
||||
param => param.name,
|
||||
),
|
||||
});
|
||||
},
|
||||
exit(path, state): void {
|
||||
state.parentTypeAliases?.delete(path.node.id.name);
|
||||
popLayer(state, 'typeAlias');
|
||||
},
|
||||
},
|
||||
TSUnionType: {
|
||||
enter(path, state): void {
|
||||
pushLayer(state, {type: 'union'});
|
||||
},
|
||||
exit(path, state): void {
|
||||
popLayer(state, 'union');
|
||||
},
|
||||
},
|
||||
TSIntersectionType: {
|
||||
enter() {
|
||||
// Do nothing
|
||||
},
|
||||
exit(path, state) {
|
||||
resolveIntersection(path, state);
|
||||
},
|
||||
},
|
||||
// Classes are a very special case, because they can extend a super class,
|
||||
// and that super class can be generic. We need to track that superclass
|
||||
// either as a resolvable or unresolvable type layer.
|
||||
ClassDeclaration: {
|
||||
enter(path, state): void {
|
||||
if (!path.node.superClass || !path.node.superTypeParameters) {
|
||||
// No generic superclass, don't care
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
t.isIdentifier(path.node.superClass) &&
|
||||
canResolveBuiltinType(path.node.superClass.name)
|
||||
) {
|
||||
pushLayer(state, {type: 'resolvableType'});
|
||||
} else {
|
||||
pushLayer(state, {type: 'unresolvableType'});
|
||||
}
|
||||
},
|
||||
exit(path, state): void {
|
||||
if (!path.node.superClass || !path.node.superTypeParameters) {
|
||||
// No generic superclass, don't care
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
t.isIdentifier(path.node.superClass) &&
|
||||
canResolveBuiltinType(path.node.superClass.name)
|
||||
) {
|
||||
popLayer(state, 'resolvableType');
|
||||
} else {
|
||||
popLayer(state, 'unresolvableType');
|
||||
}
|
||||
},
|
||||
},
|
||||
TSTypeReference: {
|
||||
// Reference inlining is done top-down
|
||||
enter(path, state): void {
|
||||
if (
|
||||
t.isTSQualifiedName(path.node.typeName) &&
|
||||
!!path.node.typeParameters
|
||||
) {
|
||||
// A generic qualified name, we need to push an unresolvable layer
|
||||
pushLayer(state, {type: 'unresolvableType'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.node.typeName.type !== 'Identifier') {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeName = path.node.typeName.name;
|
||||
if (isDefiningType(state, typeName)) {
|
||||
// Skipping recursive type
|
||||
path.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeName === 'Array' || typeName === 'ReadonlyArray') {
|
||||
pushLayer(state, {type: 'array'});
|
||||
return;
|
||||
}
|
||||
|
||||
if (canResolveBuiltinType(typeName)) {
|
||||
pushLayer(state, {type: 'resolvableType'});
|
||||
|
||||
if (typeName === 'Omit') {
|
||||
pushLayer(state, {type: 'omit'});
|
||||
}
|
||||
// Builtin type, do nothing. Will be resolved on exit.
|
||||
return;
|
||||
}
|
||||
|
||||
if (insideTypeAliasLayerWithTypeParam(state, typeName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldSkipInliningType(state, typeName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
inlineType(state, path, typeName);
|
||||
},
|
||||
// Builtin-type resolution is done bottom-up
|
||||
exit(path, state) {
|
||||
if (
|
||||
t.isTSQualifiedName(path.node.typeName) &&
|
||||
!!path.node.typeParameters
|
||||
) {
|
||||
// A generic qualified name, we need to pop the unresolvable layer
|
||||
popLayer(state, 'unresolvableType');
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.node.typeName.type !== 'Identifier') {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeName = path.node.typeName.name;
|
||||
|
||||
if (typeName === 'Array' || typeName === 'ReadonlyArray') {
|
||||
popLayer(state, 'array');
|
||||
return;
|
||||
}
|
||||
|
||||
if (canResolveBuiltinType(typeName)) {
|
||||
if (typeName === 'Omit') {
|
||||
popLayer(state, 'omit');
|
||||
}
|
||||
|
||||
popLayer(state, 'resolvableType');
|
||||
resolveBuiltinType(path, state);
|
||||
return;
|
||||
}
|
||||
|
||||
state.parentTypeAliases?.delete(typeName);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Visitor state is only used internally, so we can safely cast to PluginObj<mixed>.
|
||||
module.exports = inlineTypes as $FlowFixMe as PluginObj<mixed>;
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {TSTypeResolver} from './resolveTSType';
|
||||
import type {BaseVisitorState} from './visitorState';
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import {replaceWithCleanup} from './utils';
|
||||
|
||||
const t = require('@babel/types');
|
||||
const debug = require('debug')('build-types:transforms:inlineTypes');
|
||||
|
||||
// TODO: Handle more builtin TS types
|
||||
const builtinTypeResolvers: {
|
||||
+[K: string]: (
|
||||
path: NodePath<t.TSTypeReference>,
|
||||
state: BaseVisitorState,
|
||||
tsTypeResolver?: TSTypeResolver,
|
||||
) => void,
|
||||
} = {
|
||||
Omit: (path, state, tsTypeResolver) => {
|
||||
if (
|
||||
!path.node.typeParameters ||
|
||||
path.node.typeParameters.params.length !== 2
|
||||
) {
|
||||
throw new Error(
|
||||
`Omit type must have exactly 2 type parameters. Got ${path.node.typeParameters?.params.length ?? 0}`,
|
||||
);
|
||||
}
|
||||
|
||||
const [objectType, keys] = path.node.typeParameters.params;
|
||||
// Try to resolve the second type parameter to a literal or union of literals
|
||||
const resolvedKeys = tsTypeResolver?.(keys, state.aliasToPathMap) ?? keys;
|
||||
|
||||
if (t.isTSNeverKeyword(resolvedKeys)) {
|
||||
// never is just an empty union, so we can just replace the Omit with
|
||||
// the object type
|
||||
replaceWithCleanup(state, path, objectType);
|
||||
path.skip(); // We don't want to traverse the new node
|
||||
return;
|
||||
}
|
||||
|
||||
const stringLiteralElements = (() => {
|
||||
if (t.isTSLiteralType(keys) && t.isStringLiteral(keys.literal)) {
|
||||
return [keys.literal.value];
|
||||
}
|
||||
|
||||
if (t.isTSUnionType(resolvedKeys)) {
|
||||
const unionElements = resolvedKeys.types;
|
||||
return unionElements
|
||||
.map(element =>
|
||||
t.isTSLiteralType(element) && t.isStringLiteral(element.literal)
|
||||
? element.literal
|
||||
: null,
|
||||
)
|
||||
.filter(literal => literal !== null)
|
||||
.map(literal => literal.value);
|
||||
}
|
||||
|
||||
debug(`Unsupported Omit second parameter: ${keys.type}`);
|
||||
return 'skip' as const;
|
||||
})();
|
||||
|
||||
if (stringLiteralElements === 'skip') {
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, we know that the keys are a union of string literals
|
||||
|
||||
if (!t.isTSTypeLiteral(objectType)) {
|
||||
// The parameter is not a literal, we can try to resolve it to a literal
|
||||
const constBody = tsTypeResolver?.(objectType, state.aliasToPathMap);
|
||||
|
||||
if (!constBody || !t.isTSTypeLiteral(constBody)) {
|
||||
// Resolving the parameter failed, so we cannot do anything but update
|
||||
// the second type parameter to the resolved keys.
|
||||
if (path.node.typeParameters) {
|
||||
path.node.typeParameters.params[1] = resolvedKeys;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolving the parameter succeeded, so we can compare the keys to the
|
||||
// members of the object type
|
||||
const members = constBody.members
|
||||
.map(member => {
|
||||
if (t.isIdentifier(member.key)) {
|
||||
return member.key.name;
|
||||
}
|
||||
|
||||
if (t.isLiteral(member.key)) {
|
||||
return member.key.value;
|
||||
}
|
||||
})
|
||||
.filter(member => member);
|
||||
|
||||
const overlappingKeys = stringLiteralElements.filter(key =>
|
||||
members.includes(key),
|
||||
);
|
||||
|
||||
// If there are no overlapping keys, we can just replace the Omit with
|
||||
// the object type
|
||||
if (overlappingKeys.length === 0) {
|
||||
replaceWithCleanup(state, path, objectType);
|
||||
path.skip(); // We don't want to traverse the new node
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are overlapping keys, we can leave only the relevant ones
|
||||
if (path.node.typeParameters) {
|
||||
path.node.typeParameters.params[1] = t.tsUnionType(
|
||||
overlappingKeys.map(key => t.tsLiteralType(t.stringLiteral(key))),
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// objectType is a TSTypeLiteral, so we can just remove the omitted members
|
||||
if (
|
||||
// Only performing the resolution on objects with non-computed string keys.
|
||||
!objectType.members.every(
|
||||
member =>
|
||||
(t.isTSPropertySignature(member) || t.isTSMethodSignature(member)) &&
|
||||
(t.isIdentifier(member.key) || t.isLiteral(member.key)),
|
||||
)
|
||||
) {
|
||||
debug(`Unsupported Omit first parameter: ${objectType.type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
replaceWithCleanup(
|
||||
state,
|
||||
path,
|
||||
t.tsTypeLiteral(
|
||||
objectType.members.filter(member => {
|
||||
const propNode = (member as $FlowFixMe as t.TSPropertySignature)
|
||||
.key as $FlowFixMe as t.Identifier | t.Literal;
|
||||
const propName =
|
||||
propNode.type === 'Identifier' ? propNode.name : propNode.value;
|
||||
|
||||
return !stringLiteralElements.includes(propName);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
path.skip(); // We don't want to traverse the new node
|
||||
},
|
||||
Readonly: (path, state) => {
|
||||
if (
|
||||
!path.node.typeParameters ||
|
||||
path.node.typeParameters.params.length !== 1
|
||||
) {
|
||||
throw new Error(
|
||||
`Readonly type must have exactly 1 type parameter. Got ${path.node.typeParameters?.params.length ?? 0}`,
|
||||
);
|
||||
}
|
||||
|
||||
const [objectType] = path.node.typeParameters.params;
|
||||
|
||||
if (!t.isTSTypeLiteral(objectType)) {
|
||||
// The parameter was not inlined, so we cannot do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
replaceWithCleanup(
|
||||
state,
|
||||
path,
|
||||
t.tsTypeLiteral(
|
||||
(t.cloneDeep(objectType).members ?? []).map(member => {
|
||||
if (
|
||||
t.isTSMethodSignature(member) ||
|
||||
t.isTSCallSignatureDeclaration(member) ||
|
||||
t.isTSConstructSignatureDeclaration(member)
|
||||
) {
|
||||
return member;
|
||||
}
|
||||
member.readonly = true;
|
||||
return member;
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
path.skip(); // We don't want to traverse the new node
|
||||
},
|
||||
Partial: (path, state) => {
|
||||
if (
|
||||
!path.node.typeParameters ||
|
||||
path.node.typeParameters.params.length !== 1
|
||||
) {
|
||||
throw new Error(
|
||||
`Partial type must have exactly 1 type parameter. Got ${path.node.typeParameters?.params.length ?? 0}`,
|
||||
);
|
||||
}
|
||||
|
||||
const [objectType] = path.node.typeParameters.params;
|
||||
|
||||
if (!t.isTSTypeLiteral(objectType)) {
|
||||
// The parameter was not inlined, so we cannot do anything.
|
||||
return;
|
||||
}
|
||||
|
||||
replaceWithCleanup(
|
||||
state,
|
||||
path,
|
||||
t.tsTypeLiteral(
|
||||
(t.cloneDeep(objectType).members ?? []).map(member => {
|
||||
if (
|
||||
t.isTSMethodSignature(member) ||
|
||||
t.isTSCallSignatureDeclaration(member) ||
|
||||
t.isTSConstructSignatureDeclaration(member) ||
|
||||
t.isTSIndexSignature(member)
|
||||
) {
|
||||
return member;
|
||||
}
|
||||
member.optional = true;
|
||||
return member;
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
path.skip(); // We don't want to traverse the new node
|
||||
},
|
||||
};
|
||||
|
||||
export function canResolveBuiltinType(name: string): boolean {
|
||||
return builtinTypeResolvers.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
export function resolveBuiltinType(
|
||||
path: NodePath<t.TSTypeReference>,
|
||||
state: BaseVisitorState,
|
||||
tsTypeResolver?: TSTypeResolver,
|
||||
): void {
|
||||
const name = path.node.typeName.name;
|
||||
if (name != null && builtinTypeResolvers.hasOwnProperty(name)) {
|
||||
builtinTypeResolvers[name](path, state, tsTypeResolver);
|
||||
} else {
|
||||
debug(`Unsupported builtin type: ${name ?? 'undefined'}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {BaseVisitorState} from './visitorState';
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import {replaceWithCleanup} from './utils';
|
||||
|
||||
const t = require('@babel/types');
|
||||
const debug = require('debug')('build-types:transforms:inlineTypes');
|
||||
|
||||
export function resolveIntersection(
|
||||
path: NodePath<t.TSIntersectionType>,
|
||||
state: BaseVisitorState,
|
||||
): void {
|
||||
const newTypes: Array<BabelNodeTSType> = [];
|
||||
const requiredKeys = new Set<string>();
|
||||
const mutableKeys = new Set<string>();
|
||||
const combinedMembers: Record<
|
||||
string,
|
||||
{node: BabelNodeTSTypeAnnotation, leadingComments?: BabelNodeComment[]}[],
|
||||
> = {};
|
||||
|
||||
for (const type of path.node.types) {
|
||||
if (!t.isTSTypeLiteral(type)) {
|
||||
newTypes.push(type); // Leave it as is, nothing to do here
|
||||
continue;
|
||||
}
|
||||
|
||||
const members = type.members;
|
||||
if (members.length === 0) {
|
||||
// Empty object, lets omit it
|
||||
continue;
|
||||
}
|
||||
|
||||
const membersObj = members.reduce(
|
||||
(acc, member) => {
|
||||
if (t.isTSPropertySignature(member)) {
|
||||
const key = member.key;
|
||||
if (t.isIdentifier(key) && member.typeAnnotation) {
|
||||
acc.map[key.name] = member;
|
||||
} else if (t.isStringLiteral(key) && member.typeAnnotation) {
|
||||
acc.map[key.value] = member;
|
||||
} else {
|
||||
acc.unsupported = true;
|
||||
debug(`Unsupported object literal property key: ${key.type}`);
|
||||
}
|
||||
} else {
|
||||
acc.unsupported = true;
|
||||
debug(`Unsupported object literal type key: ${member.type}`);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
map: {} as Record<string, BabelNodeTSPropertySignature>,
|
||||
unsupported: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (membersObj.unsupported) {
|
||||
// We cannot combine this type with the others, so we leave it as is
|
||||
newTypes.push(type);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, prop] of Object.entries(membersObj.map)) {
|
||||
const annotation = prop.typeAnnotation;
|
||||
if (!annotation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[sketchy-null-bool] Missing prop is the same as false
|
||||
if (!prop.optional) {
|
||||
requiredKeys.add(key);
|
||||
}
|
||||
|
||||
// $FlowExpectedError[sketchy-null-bool] Missing prop is the same as false
|
||||
if (!prop.readonly) {
|
||||
mutableKeys.add(key);
|
||||
}
|
||||
|
||||
if (!combinedMembers[key]) {
|
||||
combinedMembers[key] = [
|
||||
{node: annotation, leadingComments: prop.leadingComments},
|
||||
];
|
||||
} else {
|
||||
combinedMembers[key].push({
|
||||
node: annotation,
|
||||
leadingComments: prop.leadingComments,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Creating a type literal from the combined keys
|
||||
const combinedLiteral = t.tsTypeLiteral(
|
||||
Object.entries(combinedMembers).map(([key, values]) => {
|
||||
const keyNode = t.isValidIdentifier(key)
|
||||
? t.identifier(key)
|
||||
: t.stringLiteral(key);
|
||||
|
||||
const prop = t.tsPropertySignature(
|
||||
keyNode,
|
||||
values.length === 1
|
||||
? values[0].node
|
||||
: t.tsTypeAnnotation(
|
||||
t.tsIntersectionType(
|
||||
values.map(value => value.node.typeAnnotation),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
prop.optional = !requiredKeys.has(key);
|
||||
prop.readonly = !mutableKeys.has(key);
|
||||
prop.leadingComments = values
|
||||
.map(value => value.leadingComments)
|
||||
.filter(Boolean)
|
||||
.flat();
|
||||
|
||||
return prop;
|
||||
}),
|
||||
);
|
||||
|
||||
if (combinedLiteral.members.length !== 0) {
|
||||
newTypes.push(combinedLiteral);
|
||||
}
|
||||
|
||||
let newNode;
|
||||
|
||||
if (newTypes.length === 0) {
|
||||
newNode = t.tsTypeReference(
|
||||
t.identifier('Record'),
|
||||
t.tsTypeParameterInstantiation([t.tsStringKeyword(), t.tsNeverKeyword()]),
|
||||
);
|
||||
} else if (newTypes.length === 1) {
|
||||
newNode = newTypes[0];
|
||||
} else {
|
||||
newNode = t.tsIntersectionType(newTypes);
|
||||
}
|
||||
|
||||
const alias = state.nodeToAliasMap?.get(path.node);
|
||||
if (alias !== undefined) {
|
||||
// Inheriting alias from the node we're replacing
|
||||
state.nodeToAliasMap.set(newNode, alias);
|
||||
state.nodeToAliasMap.delete(path.node);
|
||||
}
|
||||
|
||||
replaceWithCleanup(state, path, newNode);
|
||||
path.skip(); // We don't want to traverse the new node
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import traverse from '@babel/traverse';
|
||||
|
||||
const inlineTypes = require('./inlineTypesVisitor');
|
||||
const t = require('@babel/types');
|
||||
|
||||
export type TSTypeResolver = (
|
||||
node: BabelNodeTSType,
|
||||
aliasToPathMap: Map<string, NodePath<t.TSTypeAliasDeclaration>>,
|
||||
) => ?BabelNodeTSType;
|
||||
|
||||
export const resolveTSType: TSTypeResolver = (node, aliasToPathMap) => {
|
||||
if (t.isTSTypeLiteral(node)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!t.isTSTypeLiteral(node)) {
|
||||
let body: ?BabelNodeTSType = null;
|
||||
const wrapperAst = t.file(
|
||||
t.program([
|
||||
t.exportNamedDeclaration(
|
||||
t.tsTypeAliasDeclaration(
|
||||
t.identifier('__WrapperAlias'),
|
||||
undefined,
|
||||
t.cloneDeep(node),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
traverse(wrapperAst, inlineTypes.visitor, undefined, {
|
||||
aliasToPathMap: aliasToPathMap,
|
||||
});
|
||||
|
||||
traverse(wrapperAst, {
|
||||
TSTypeAliasDeclaration(innerPath) {
|
||||
if (innerPath.node.id.name !== '__WrapperAlias') {
|
||||
return;
|
||||
}
|
||||
|
||||
body = innerPath.node.typeAnnotation;
|
||||
innerPath.stop();
|
||||
},
|
||||
});
|
||||
|
||||
if (!body) {
|
||||
return;
|
||||
}
|
||||
const constBody = body;
|
||||
|
||||
if (t.isTSTypeLiteral(constBody)) {
|
||||
// Only performing the resolution on objects with non-computed string keys.
|
||||
if (
|
||||
!constBody.members.every(
|
||||
member =>
|
||||
(t.isTSPropertySignature(member) ||
|
||||
t.isTSMethodSignature(member)) &&
|
||||
(t.isIdentifier(member.key) || t.isLiteral(member.key)),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return constBody;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {BaseVisitorState} from './visitorState';
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import {replaceWithCleanup} from './utils';
|
||||
|
||||
const t = require('@babel/types');
|
||||
const debug = require('debug')('build-types:transforms:inlineTypes');
|
||||
|
||||
const typeOperatorResolvers: {
|
||||
+[K: string]: (
|
||||
path: NodePath<t.TSTypeOperator>,
|
||||
state: BaseVisitorState,
|
||||
) => void,
|
||||
} = {
|
||||
keyof: (path, state) => {
|
||||
const unionElements: Array<BabelNodeTSType> = [];
|
||||
|
||||
const typeAnnotation = path.node.typeAnnotation;
|
||||
if (t.isTSTypeLiteral(typeAnnotation)) {
|
||||
for (const member of typeAnnotation.members) {
|
||||
if (t.isTSPropertySignature(member) || t.isTSMethodSignature(member)) {
|
||||
if (t.isIdentifier(member.key)) {
|
||||
unionElements.push(
|
||||
t.tsLiteralType(t.stringLiteral(member.key.name)),
|
||||
);
|
||||
} else if (t.isStringLiteral(member.key)) {
|
||||
unionElements.push(
|
||||
t.tsLiteralType(t.stringLiteral(member.key.value)),
|
||||
);
|
||||
} else {
|
||||
debug(
|
||||
`Unsupported object literal property key: ${member.key.type}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
debug(`Unsupported object literal type key: ${member.type}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug(`Unsupported type operand for keyof: ${typeAnnotation.type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
replaceWithCleanup(
|
||||
state,
|
||||
path,
|
||||
unionElements.length === 0
|
||||
? t.tsNeverKeyword()
|
||||
: t.tsUnionType(unionElements),
|
||||
);
|
||||
|
||||
path.skip(); // We don't want to traverse the new node
|
||||
},
|
||||
};
|
||||
|
||||
export function canResolveTypeOperator(name: string): boolean {
|
||||
return typeOperatorResolvers.hasOwnProperty(name);
|
||||
}
|
||||
|
||||
export function resolveTypeOperator(
|
||||
path: NodePath<t.TSTypeOperator>,
|
||||
state: BaseVisitorState,
|
||||
): void {
|
||||
const name = path.node.operator;
|
||||
if (canResolveTypeOperator(name)) {
|
||||
typeOperatorResolvers[name](path, state);
|
||||
} else {
|
||||
debug(`Unsupported type operator: ${name}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {BaseVisitorState, InlineVisitorState} from './visitorState';
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
import {
|
||||
insideArrayLayer,
|
||||
insideExtendsLayer,
|
||||
insideKeyofLayer,
|
||||
insideOmitLayer,
|
||||
insideUnionLayer,
|
||||
insideUnresolvableTypeInstantiation,
|
||||
} from './contextStack';
|
||||
|
||||
const t = require('@babel/types');
|
||||
|
||||
export function onNodeExit(state: BaseVisitorState, node: t.Node): void {
|
||||
// We're no longer inside the node if we remove it
|
||||
const alias = state.nodeToAliasMap?.get(node);
|
||||
if (alias !== undefined) {
|
||||
state.parentTypeAliases?.delete(alias);
|
||||
}
|
||||
}
|
||||
|
||||
export function replaceWithCleanup(
|
||||
state: BaseVisitorState,
|
||||
path: NodePath<t.Node>,
|
||||
newNode: t.Node,
|
||||
) {
|
||||
if (newNode === path.node) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// Treating `newNode` as a template, and instantiating it
|
||||
const clonedNewNode = t.cloneDeep(newNode);
|
||||
|
||||
// We're removing the node, so let's treat it as if we're exiting it
|
||||
onNodeExit(state, path.node);
|
||||
|
||||
path.replaceWith(clonedNewNode);
|
||||
}
|
||||
|
||||
export function shouldSkipInliningType(
|
||||
state: InlineVisitorState,
|
||||
alias: string,
|
||||
): boolean {
|
||||
// Type instantiations of types that are not resolvable
|
||||
if (insideUnresolvableTypeInstantiation(state)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skipping inline of union/array types, except when inside keyof or omit
|
||||
if (
|
||||
(insideUnionLayer(state) ||
|
||||
insideArrayLayer(state) ||
|
||||
insideExtendsLayer(state)) &&
|
||||
!(insideKeyofLayer(state) || insideOmitLayer(state))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @oncall react_native
|
||||
*/
|
||||
|
||||
import type {StackLayer} from './contextStack';
|
||||
import type {NodePath} from '@babel/traverse';
|
||||
|
||||
const t = require('@babel/types');
|
||||
|
||||
export type BaseVisitorState = {
|
||||
aliasToPathMap: Map<string, NodePath<t.TSTypeAliasDeclaration>>,
|
||||
parentTypeAliases?: Set<string>,
|
||||
nodeToAliasMap: Map<t.Node, string>,
|
||||
...
|
||||
};
|
||||
|
||||
export type InlineVisitorState = {
|
||||
...BaseVisitorState,
|
||||
/**
|
||||
* Layers of operators that the current node is inside of.
|
||||
* Used to determine if we should inline a type.
|
||||
*/
|
||||
stack: Array<StackLayer>,
|
||||
...
|
||||
};
|
||||
Reference in New Issue
Block a user