From dd6c38b05e80709e553300300462e1e5a0fd1b50 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 8 Aug 2024 06:30:05 -0700 Subject: [PATCH] Require Flow types in all react-native source files (#45786) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/45786 Following D60377082, `packages/react-native/Libraries/` is now 100% parsable by `flow-api-translator` and covered by `flow-api-test`. This diff increases test strictness to preserve this state going forward. Changelog: [Internal] Reviewed By: cortinico Differential Revision: D60377123 --- .../__snapshots__/public-api-test.js.snap | 334 ------------------ .../Libraries/__tests__/public-api-test.js | 70 ++-- 2 files changed, 29 insertions(+), 375 deletions(-) diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index edf531fe27e913..327be499595416 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -7787,340 +7787,6 @@ exports[`public API should not change unintentionally Libraries/ReactPrivate/Rea " `; -exports[`public API should not change unintentionally Libraries/Renderer/implementations/ReactFabric-dev.js 1`] = `"UNTYPED MODULE"`; - -exports[`public API should not change unintentionally Libraries/Renderer/implementations/ReactFabric-prod.js 1`] = `"UNTYPED MODULE"`; - -exports[`public API should not change unintentionally Libraries/Renderer/implementations/ReactFabric-profiling.js 1`] = `"UNTYPED MODULE"`; - -exports[`public API should not change unintentionally Libraries/Renderer/implementations/ReactNativeRenderer-dev.js 1`] = `"UNTYPED MODULE"`; - -exports[`public API should not change unintentionally Libraries/Renderer/implementations/ReactNativeRenderer-prod.js 1`] = `"UNTYPED MODULE"`; - -exports[`public API should not change unintentionally Libraries/Renderer/implementations/ReactNativeRenderer-profiling.js 1`] = `"UNTYPED MODULE"`; - -exports[`public API should not change unintentionally Libraries/Renderer/shims/ReactFabric.js 1`] = ` -"declare module.exports: ReactFabricType; -" -`; - -exports[`public API should not change unintentionally Libraries/Renderer/shims/ReactFeatureFlags.js 1`] = ` -"declare const ReactFeatureFlags: { debugRenderPhaseSideEffects: false }; -declare module.exports: ReactFeatureFlags; -" -`; - -exports[`public API should not change unintentionally Libraries/Renderer/shims/ReactNative.js 1`] = ` -"declare module.exports: ReactNativeType; -" -`; - -exports[`public API should not change unintentionally Libraries/Renderer/shims/ReactNativeTypes.js 1`] = ` -"export type MeasureOnSuccessCallback = ( - x: number, - y: number, - width: number, - height: number, - pageX: number, - pageY: number -) => void; -export type MeasureInWindowOnSuccessCallback = ( - x: number, - y: number, - width: number, - height: number -) => void; -export type MeasureLayoutOnSuccessCallback = ( - left: number, - top: number, - width: number, - height: number -) => void; -export type AttributeType = - | true - | $ReadOnly<{ - diff?: (arg1: T, arg2: T) => boolean, - process?: (arg1: V) => T, - }>; -export type AnyAttributeType = AttributeType<$FlowFixMe, $FlowFixMe>; -export type AttributeConfiguration = $ReadOnly<{ - [propName: string]: AnyAttributeType, - style: $ReadOnly<{ - [propName: string]: AnyAttributeType, - ... - }>, - ... -}>; -export type PartialAttributeConfiguration = $ReadOnly<{ - [propName: string]: AnyAttributeType, - style?: $ReadOnly<{ - [propName: string]: AnyAttributeType, - ... - }>, - ... -}>; -export type ViewConfig = $ReadOnly<{ - Commands?: $ReadOnly<{ [commandName: string]: number, ... }>, - Constants?: $ReadOnly<{ [name: string]: mixed, ... }>, - Manager?: string, - NativeProps?: $ReadOnly<{ [propName: string]: string, ... }>, - baseModuleName?: ?string, - bubblingEventTypes?: $ReadOnly<{ - [eventName: string]: $ReadOnly<{ - phasedRegistrationNames: $ReadOnly<{ - captured: string, - bubbled: string, - skipBubbling?: ?boolean, - }>, - }>, - ... - }>, - directEventTypes?: $ReadOnly<{ - [eventName: string]: $ReadOnly<{ - registrationName: string, - }>, - ... - }>, - supportsRawText?: boolean, - uiViewClassName: string, - validAttributes: AttributeConfiguration, -}>; -export type PartialViewConfig = $ReadOnly<{ - bubblingEventTypes?: $PropertyType, - directEventTypes?: $PropertyType, - supportsRawText?: boolean, - uiViewClassName: string, - validAttributes?: PartialAttributeConfiguration, -}>; -export interface INativeMethods { - blur(): void; - focus(): void; - measure(callback: MeasureOnSuccessCallback): void; - measureInWindow(callback: MeasureInWindowOnSuccessCallback): void; - measureLayout( - relativeToNativeNode: number | ElementRef>, - onSuccess: MeasureLayoutOnSuccessCallback, - onFail?: () => void - ): void; - setNativeProps(nativeProps: { ... }): void; -} -export type NativeMethods = $ReadOnly<{| - blur(): void, - focus(): void, - measure(callback: MeasureOnSuccessCallback): void, - measureInWindow(callback: MeasureInWindowOnSuccessCallback): void, - measureLayout( - relativeToNativeNode: number | ElementRef>, - onSuccess: MeasureLayoutOnSuccessCallback, - onFail?: () => void - ): void, - setNativeProps(nativeProps: { ... }): void, -|}>; -export type HostComponent = AbstractComponent>; -type SecretInternalsType = { - computeComponentStackForErrorReporting(tag: number): string, - ... -}; -type InspectorDataProps = $ReadOnly<{ - [propName: string]: string, - ... -}>; -type InspectorDataGetter = ( - ( - componentOrHandle: ElementRef | number - ) => ?number -) => $ReadOnly<{ - measure: (callback: MeasureOnSuccessCallback) => void, - props: InspectorDataProps, -}>; -export type InspectorData = $ReadOnly<{ - closestInstance?: mixed, - hierarchy: Array<{ - name: ?string, - getInspectorData: InspectorDataGetter, - }>, - selectedIndex: ?number, - props: InspectorDataProps, - componentStack: string, -}>; -export type TouchedViewDataAtPoint = $ReadOnly<{ - pointerY: number, - touchedViewTag?: number, - frame: $ReadOnly<{ - top: number, - left: number, - width: number, - height: number, - }>, - ...InspectorData, -}>; -export type RenderRootOptions = { - onUncaughtError?: ( - error: mixed, - errorInfo: { +componentStack?: ?string } - ) => void, - onCaughtError?: ( - error: mixed, - errorInfo: { - +componentStack?: ?string, - +errorBoundary?: ?React$Component, - } - ) => void, - onRecoverableError?: ( - error: mixed, - errorInfo: { +componentStack?: ?string } - ) => void, -}; -export type ReactNativeType = { - findHostInstance_DEPRECATED( - componentOrHandle: ?(ElementRef | number) - ): ?ElementRef>, - findNodeHandle( - componentOrHandle: ?(ElementRef | number) - ): ?number, - isChildPublicInstance( - parent: PublicInstance | HostComponent, - child: PublicInstance | HostComponent - ): boolean, - dispatchCommand( - handle: ElementRef>, - command: string, - args: Array - ): void, - sendAccessibilityEvent( - handle: ElementRef>, - eventType: string - ): void, - render( - element: Element, - containerTag: number, - callback: ?() => void, - options: ?RenderRootOptions - ): ?ElementRef, - unmountComponentAtNode(containerTag: number): void, - unmountComponentAtNodeAndRemoveContainer(containerTag: number): void, - unstable_batchedUpdates: (fn: (T) => void, bookkeeping: T) => void, - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: SecretInternalsType, - ... -}; -declare export opaque type Node; -declare export opaque type InternalInstanceHandle; -type PublicInstance = mixed; -type PublicTextInstance = mixed; -export type ReactFabricType = { - findHostInstance_DEPRECATED( - componentOrHandle: ?(ElementRef | number) - ): ?ElementRef>, - findNodeHandle( - componentOrHandle: ?(ElementRef | number) - ): ?number, - dispatchCommand( - handle: ElementRef>, - command: string, - args: Array - ): void, - isChildPublicInstance(parent: PublicInstance, child: PublicInstance): boolean, - sendAccessibilityEvent( - handle: ElementRef>, - eventType: string - ): void, - render( - element: Element, - containerTag: number, - callback: ?() => void, - concurrentRoot: ?boolean, - options: ?RenderRootOptions - ): ?ElementRef, - unmountComponentAtNode(containerTag: number): void, - getNodeFromInternalInstanceHandle( - internalInstanceHandle: InternalInstanceHandle - ): ?Node, - getPublicInstanceFromInternalInstanceHandle( - internalInstanceHandle: InternalInstanceHandle - ): PublicInstance | PublicTextInstance | null, - ... -}; -export type ReactFabricEventTouch = { - identifier: number, - locationX: number, - locationY: number, - pageX: number, - pageY: number, - screenX: number, - screenY: number, - target: number, - timestamp: number, - force: number, - ... -}; -export type ReactFabricEvent = { - touches: Array, - changedTouches: Array, - targetTouches: Array, - target: number, - ... -}; -export type LayoutAnimationType = - | \\"spring\\" - | \\"linear\\" - | \\"easeInEaseOut\\" - | \\"easeIn\\" - | \\"easeOut\\" - | \\"keyboard\\"; -export type LayoutAnimationProperty = - | \\"opacity\\" - | \\"scaleX\\" - | \\"scaleY\\" - | \\"scaleXY\\"; -export type LayoutAnimationAnimationConfig = $ReadOnly<{ - duration?: number, - delay?: number, - springDamping?: number, - initialVelocity?: number, - type?: LayoutAnimationType, - property?: LayoutAnimationProperty, -}>; -export type LayoutAnimationConfig = $ReadOnly<{ - duration: number, - create?: LayoutAnimationAnimationConfig, - update?: LayoutAnimationAnimationConfig, - delete?: LayoutAnimationAnimationConfig, -}>; -" -`; - -exports[`public API should not change unintentionally Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js 1`] = ` -"declare export const customBubblingEventTypes: { - [eventName: string]: $ReadOnly<{ - phasedRegistrationNames: $ReadOnly<{ - captured: string, - bubbled: string, - skipBubbling?: ?boolean, - }>, - }>, -}; -declare export const customDirectEventTypes: { - [eventName: string]: $ReadOnly<{ - registrationName: string, - }>, -}; -declare export function register( - name: string, - callback: () => ViewConfig -): string; -declare export function get(name: string): ViewConfig; -" -`; - -exports[`public API should not change unintentionally Libraries/Renderer/shims/createReactNativeComponentClass.js 1`] = ` -"declare const createReactNativeComponentClass: ( - name: string, - callback: () => ViewConfig -) => string; -declare module.exports: createReactNativeComponentClass; -" -`; - exports[`public API should not change unintentionally Libraries/Settings/NativeSettingsManager.js 1`] = ` "export * from \\"../../src/private/specs/modules/NativeSettingsManager\\"; declare export default typeof NativeSettingsManager; diff --git a/packages/react-native/Libraries/__tests__/public-api-test.js b/packages/react-native/Libraries/__tests__/public-api-test.js index 40b482d6b6438e..08cdde71acf5ae 100644 --- a/packages/react-native/Libraries/__tests__/public-api-test.js +++ b/packages/react-native/Libraries/__tests__/public-api-test.js @@ -26,12 +26,11 @@ const IGNORE_PATTERNS = [ '**/*.fb.js', '**/*.macos.js', '**/*.windows.js', + // Non source files + 'Libraries/Renderer/implementations/**', + 'Libraries/Renderer/shims/**', ]; -// Exclude list for files that fail to parse under flow-api-translator. Please -// review your changes before adding new entries. -const FILES_WITH_KNOWN_ERRORS = new Set([]); - const sourceFiles = [ 'index.js', ...glob.sync(JS_FILES_PATTERN, { @@ -46,47 +45,36 @@ describe('public API', () => { test.each(sourceFiles)('%s', async (file: string) => { const source = await fs.readFile(path.join(PACKAGE_ROOT, file), 'utf-8'); - if (/@flow/.test(source)) { - // Require and use adjacent .js.flow file when source file includes an - // unsupported-syntax suppression - if (source.includes('// $FlowFixMe[unsupported-syntax]')) { - const flowDefPath = path.join( - PACKAGE_ROOT, - file.replace('.js', '.js.flow'), - ); - - if (!existsSync(flowDefPath)) { - throw new Error( - 'Found an unsupported-syntax suppression in ' + - file + - ', meaning types cannot be parsed. Add an adjacent .js.flow file to fix this!', - ); - } + if (!/@flow/.test(source)) { + throw new Error( + file + + ' is untyped. All source files in the react-native package must be written using Flow (// @flow).', + ); + } - return; + // Require and use adjacent .js.flow file when source file includes an + // unsupported-syntax suppression + if (source.includes('// $FlowFixMe[unsupported-syntax]')) { + const flowDefPath = path.join( + PACKAGE_ROOT, + file.replace('.js', '.js.flow'), + ); + + if (!existsSync(flowDefPath)) { + throw new Error( + 'Found an unsupported-syntax suppression in ' + + file + + ', meaning types cannot be parsed. Add an adjacent .js.flow file to fix this!', + ); } - let success = false; - try { - expect(await translateFlowToExportedAPI(source)).toMatchSnapshot(); + return; + } - success = true; - } catch (e) { - if (!FILES_WITH_KNOWN_ERRORS.has(file)) { - throw new Error( - 'Unable to parse file: ' + file + '\n\n' + e.message, - ); - } - } finally { - if (success && FILES_WITH_KNOWN_ERRORS.has(file)) { - throw new Error( - 'Expected parse error, please remove file exclude from FILES_WITH_KNOWN_ERRORS: ' + - file, - ); - } - } - } else { - expect('UNTYPED MODULE').toMatchSnapshot(); + try { + expect(await translateFlowToExportedAPI(source)).toMatchSnapshot(); + } catch (e) { + throw new Error('Unable to parse file: ' + file + '\n\n' + e.message); } }); });