From 2f7d19a14ea6bd595349b0d3aeec6ca08bb25f75 Mon Sep 17 00:00:00 2001 From: Dimitar Nestorov Date: Fri, 20 Jan 2023 18:15:39 +0200 Subject: [PATCH 1/2] fix: bloated `lib/**/*.d.ts` files --- src/components/Appbar/AppbarAction.tsx | 3 ++- src/components/Appbar/AppbarBackAction.tsx | 3 ++- src/components/FAB/FAB.tsx | 3 ++- src/components/IconButton/IconButton.tsx | 3 ++- src/components/Searchbar.tsx | 3 ++- src/components/TextInput/TextInput.tsx | 3 ++- src/utils/forwardRef.tsx | 23 ++++++++++++++++++++++ 7 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 src/utils/forwardRef.tsx diff --git a/src/components/Appbar/AppbarAction.tsx b/src/components/Appbar/AppbarAction.tsx index 61f82970e9..f3e55f959d 100644 --- a/src/components/Appbar/AppbarAction.tsx +++ b/src/components/Appbar/AppbarAction.tsx @@ -6,6 +6,7 @@ import type { ThemeProp } from 'src/types'; import { useInternalTheme } from '../../core/theming'; import { black } from '../../styles/themes/v2/colors'; +import { forwardRef } from '../../utils/forwardRef'; import type { IconSource } from '../Icon'; import IconButton from '../IconButton/IconButton'; @@ -73,7 +74,7 @@ export type Props = React.ComponentPropsWithoutRef & { * export default MyComponent; * ``` */ -const AppbarAction = React.forwardRef( +const AppbarAction = forwardRef( ( { size = 24, diff --git a/src/components/Appbar/AppbarBackAction.tsx b/src/components/Appbar/AppbarBackAction.tsx index 5b88ea7227..c8b8bd009c 100644 --- a/src/components/Appbar/AppbarBackAction.tsx +++ b/src/components/Appbar/AppbarBackAction.tsx @@ -6,6 +6,7 @@ import type { View, } from 'react-native'; +import { forwardRef } from '../../utils/forwardRef'; import type { $Omit } from './../../types'; import AppbarAction from './AppbarAction'; import AppbarBackIcon from './AppbarBackIcon'; @@ -60,7 +61,7 @@ export type Props = $Omit< * export default MyComponent; * ``` */ -const AppbarBackAction = React.forwardRef( +const AppbarBackAction = forwardRef( ({ accessibilityLabel = 'Back', ...rest }: Props, ref) => ( & { * export default MyComponent; * ``` */ -const FAB = React.forwardRef( +const FAB = forwardRef( ( { icon, diff --git a/src/components/IconButton/IconButton.tsx b/src/components/IconButton/IconButton.tsx index 03a9ffc01d..21f8acec4a 100644 --- a/src/components/IconButton/IconButton.tsx +++ b/src/components/IconButton/IconButton.tsx @@ -9,6 +9,7 @@ import { import { useInternalTheme } from '../../core/theming'; import type { $RemoveChildren, ThemeProp } from '../../types'; +import { forwardRef } from '../../utils/forwardRef'; import CrossFadeIcon from '../CrossFadeIcon'; import Icon, { IconSource } from '../Icon'; import Surface from '../Surface'; @@ -112,7 +113,7 @@ export type Props = $RemoveChildren & { * * @extends TouchableRipple props https://callstack.github.io/react-native-paper/touchable-ripple.html */ -const IconButton = React.forwardRef( +const IconButton = forwardRef( ( { icon, diff --git a/src/components/Searchbar.tsx b/src/components/Searchbar.tsx index 80879e2ffd..b537e2ed2b 100644 --- a/src/components/Searchbar.tsx +++ b/src/components/Searchbar.tsx @@ -17,6 +17,7 @@ import color from 'color'; import { useInternalTheme } from '../core/theming'; import type { ThemeProp } from '../types'; +import { forwardRef } from '../utils/forwardRef'; import ActivityIndicator from './ActivityIndicator'; import type { IconSource } from './Icon'; import IconButton from './IconButton/IconButton'; @@ -119,7 +120,7 @@ type TextInputHandles = Pick< * ``` */ -const Searchbar = React.forwardRef( +const Searchbar = forwardRef( ( { clearAccessibilityLabel = 'clear', diff --git a/src/components/TextInput/TextInput.tsx b/src/components/TextInput/TextInput.tsx index de21ffde0a..1398d8b004 100644 --- a/src/components/TextInput/TextInput.tsx +++ b/src/components/TextInput/TextInput.tsx @@ -10,6 +10,7 @@ import { import { useInternalTheme } from '../../core/theming'; import type { ThemeProp } from '../../types'; +import { forwardRef } from '../../utils/forwardRef'; import TextInputAffix, { Props as TextInputAffixProps, } from './Adornment/TextInputAffix'; @@ -220,7 +221,7 @@ type TextInputHandles = Pick< * @extends TextInput props https://reactnative.dev/docs/textinput#props */ -const TextInput = React.forwardRef( +const TextInput = forwardRef( ( { mode = 'flat', diff --git a/src/utils/forwardRef.tsx b/src/utils/forwardRef.tsx new file mode 100644 index 0000000000..8392a0c624 --- /dev/null +++ b/src/utils/forwardRef.tsx @@ -0,0 +1,23 @@ +import { + forwardRef as reactForwardRef, + ForwardRefRenderFunction, + PropsWithoutRef, + RefAttributes, + ForwardRefExoticComponent, +} from 'react'; + +export type ForwarRefComponent = ForwardRefExoticComponent< + PropsWithoutRef

& RefAttributes +>; + +/** + * TypeScript generated a large union of props from `ViewProps` in + * `d.ts` files when using `React.forwardRef`. To prevent this + * `ForwarRefComponent` was created and exported. Use this + * `forwardRef` instead of `React.forwardRef` so you don't have to + * import `ForwarRefComponent`. + * More info: https://github.com/callstack/react-native-paper/pull/3603 + */ +export const forwardRef: ( + render: ForwardRefRenderFunction +) => ForwarRefComponent = reactForwardRef; From 5adc5dfb2f9cccbd2425d375ec34a3f5f805e434 Mon Sep 17 00:00:00 2001 From: Dimitar Nestorov Date: Fri, 20 Jan 2023 18:15:40 +0200 Subject: [PATCH 2/2] ci: lint TypeScript output script --- .circleci/config.yml | 1 + scripts/typescript-output-lint.js | 113 ++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 scripts/typescript-output-lint.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 8f98a2d0b9..33e0070e34 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -95,6 +95,7 @@ jobs: name: Build package command: | yarn prepare + node ./scripts/typescript-output-lint build-docs: executor: default diff --git a/scripts/typescript-output-lint.js b/scripts/typescript-output-lint.js new file mode 100644 index 0000000000..ff396075b4 --- /dev/null +++ b/scripts/typescript-output-lint.js @@ -0,0 +1,113 @@ +const { promises: fs } = require('fs'); +const path = require('path'); + +const root = path.resolve(__dirname, '..'); +const output = path.join(root, 'lib', 'typescript'); + +/** + * List of React Native props not used by React Native Paper. + * Feel free to delete props from this list when + * React Native Paper has defined/uses one with the same name. + * This script was originally created to detect if TypeScript + * has exported a large union of props from ViewProps. + * More info: https://github.com/callstack/react-native-paper/pull/3603 + */ +const unusedViewProps = [ + 'nativeID', + 'accessibilityActions', + 'accessibilityRole', + 'accessibilityValue', + 'onAccessibilityAction', + 'accessibilityLabelledBy', + 'accessibilityLiveRegion', + 'accessibilityLanguage', + 'accessibilityViewIsModal', + 'onAccessibilityEscape', + 'onAccessibilityTap', + 'onMagicTap', + 'accessibilityIgnoresInvertColors', + 'hitSlop', + 'removeClippedSubviews', + 'collapsable', + 'needsOffscreenAlphaCompositing', + 'renderToHardwareTextureAndroid', + 'shouldRasterizeIOS', + 'isTVSelectable', + 'hasTVPreferredFocus', + 'tvParallaxProperties', + 'tvParallaxShiftDistanceX', + 'tvParallaxShiftDistanceY', + 'tvParallaxTiltAngle', + 'tvParallaxMagnification', + 'onStartShouldSetResponder', + 'onMoveShouldSetResponder', + 'onResponderEnd', + 'onResponderGrant', + 'onResponderReject', + 'onResponderMove', + 'onResponderRelease', + 'onResponderStart', + 'onResponderTerminationRequest', + 'onResponderTerminate', + 'onStartShouldSetResponderCapture', + 'onMoveShouldSetResponderCapture', + 'onTouchStart', + 'onTouchMove', + 'onTouchEnd', + 'onTouchCancel', + 'onTouchEndCapture', + 'onPointerEnter', + 'onPointerEnterCapture', + 'onPointerLeave', + 'onPointerLeaveCapture', + 'onPointerMove', + 'onPointerMoveCapture', + 'onPointerCancel', + 'onPointerCancelCapture', + 'onPointerDown', + 'onPointerDownCapture', + 'onPointerUp', + 'onPointerUpCapture', + 'onHoverIn', + 'onHoverOut', + 'cancelable', + 'delayHoverIn', + 'delayHoverOut', + 'pressRetentionOffset', + 'android_disableSound', + 'android_ripple', + 'testOnly_pressed', + 'unstable_pressDelay', +]; + +async function* getFiles(directory) { + const entries = await fs.readdir(directory, { withFileTypes: true }); + for (const entry of entries) { + const res = path.resolve(directory, entry.name); + if (entry.isDirectory()) { + yield* getFiles(res); + } else { + yield res; + } + } +} + +async function main() { + for await (const file of getFiles(output)) { + const content = await fs.readFile(file); + for (const prop of unusedViewProps) { + if (content.includes(prop)) { + throw new Error( + `Found text '${prop}' in '${file}'. Please use the wrapped 'forwardRef' in 'src/utils/forwardRef.ts', export some return types, or modify 'scripts/typescript-output-lint.js'` + ); + } + } + } + + console.log('✅ No React Native props mentioned in TypeScript files'); +} + +main().catch((reason) => { + console.error(reason); + process.exit(1); +});