mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
191ddc1ec7
Summary: Changelog: [GENERAL] [FIXED] - Fixed babel plugin validation error when coverage instrumentation is enabled Pull Request resolved: https://github.com/facebook/react-native/pull/53381 ### Problem [Workplace post](https://fb.workplace.com/groups/235694244595999/permalink/1278937163605030/) React Native tests were failing **only when coverage collection was enabled** with the error: `'Commands' is a reserved export and may only be used to export the result of codegenNativeCommands.` ### Root Cause The React Native Babel plugin's `codegenNativeCommands` validation logic only handled direct `CallExpression` AST nodes. When coverage instrumentation was enabled, it transformed: **Normal code:** `export const Commands = codegenNativeCommands<NativeCommands>({...})` **With coverage:** `export const Commands = (cov_xxx().s[0]++, codegenNativeCommands<NativeCommands>({...}))` The plugin failed to recognize the valid `codegenNativeCommands` call wrapped in a `SequenceExpression` by coverage instrumentation. ### **Solution** Added `isCodegenNativeCommandsDeclaration` function to handle: 1. **Coverage instrumentation**: `SequenceExpression` nodes containing the function call 2. **Flow type casts**: `TypeCastExpression` and `AsExpression` 3. **TypeScript assertions**: `TSAsExpression` 4. **Direct calls**: Original `CallExpression` (backward compatibility) Reviewed By: andrewdacenko Differential Revision: D80572666 fbshipit-source-id: 465f4312a0229d8a92e495c685f46b607ce326e4
228 lines
6.0 KiB
JavaScript
228 lines
6.0 KiB
JavaScript
/**
|
|
* 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
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const COMMANDS_EXPORTED_WITH_DIFFERENT_NAME = `
|
|
// @flow
|
|
|
|
const codegenNativeComponent = require('codegenNativeComponent');
|
|
|
|
import type {ViewProps} from 'ViewPropTypes';
|
|
import type {NativeComponentType} from 'codegenNativeComponent';
|
|
|
|
type ModuleProps = $ReadOnly<{|
|
|
...ViewProps,
|
|
|}>;
|
|
|
|
type NativeType = NativeComponentType<ModuleProps>;
|
|
|
|
interface NativeCommands {
|
|
+hotspotUpdate: (viewRef: React.ElementRef<NativeType>) => void;
|
|
}
|
|
|
|
export const Foo = codegenNativeCommands<NativeCommands>();
|
|
|
|
export default (codegenNativeComponent<ModuleProps>('Module'): NativeType);
|
|
`;
|
|
|
|
const OTHER_COMMANDS_EXPORT = `
|
|
// @flow
|
|
|
|
const codegenNativeComponent = require('codegenNativeComponent');
|
|
|
|
import type {ViewProps} from 'ViewPropTypes';
|
|
import type {NativeComponentType} from 'codegenNativeComponent';
|
|
|
|
type ModuleProps = $ReadOnly<{|
|
|
...ViewProps,
|
|
|}>;
|
|
|
|
type NativeType = NativeComponentType<ModuleProps>;
|
|
|
|
interface NativeCommands {
|
|
+hotspotUpdate: (viewRef: React.ElementRef<NativeType>) => void;
|
|
}
|
|
|
|
export const Commands = 4;
|
|
|
|
export default (codegenNativeComponent<ModuleProps>('Module'): NativeType);
|
|
`;
|
|
|
|
const COMMANDS_EXPORTED_WITH_SHORTHAND = `
|
|
// @flow
|
|
|
|
const codegenNativeComponent = require('codegenNativeComponent');
|
|
import type {NativeComponentType} from 'codegenNativeComponent';
|
|
|
|
import type {ViewProps} from 'ViewPropTypes';
|
|
|
|
type ModuleProps = $ReadOnly<{|
|
|
...ViewProps,
|
|
|}>;
|
|
|
|
type NativeType = NativeComponentType<ModuleProps>;
|
|
|
|
interface NativeCommands {
|
|
+hotspotUpdate: (viewRef: React.ElementRef<NativeType>) => void;
|
|
}
|
|
|
|
const Commands = 4;
|
|
|
|
export {Commands};
|
|
|
|
export default (codegenNativeComponent<ModuleProps>('Module'): NativeType);
|
|
`;
|
|
|
|
const COMMANDS_WITH_COVERAGE_INVALID = `
|
|
// @flow
|
|
|
|
const codegenNativeComponent = require('codegenNativeComponent');
|
|
import type {NativeComponentType} from 'codegenNativeComponent';
|
|
|
|
import type {ViewProps} from 'ViewPropTypes';
|
|
|
|
type ModuleProps = $ReadOnly<{|
|
|
...ViewProps,
|
|
|}>;
|
|
|
|
type NativeType = NativeComponentType<ModuleProps>;
|
|
|
|
// Coverage instrumentation of invalid Commands export - should still fail
|
|
export const Commands = (cov_1234567890().s[0]++, {
|
|
hotspotUpdate: () => {},
|
|
scrollTo: () => {},
|
|
});
|
|
|
|
export default (codegenNativeComponent<ModuleProps>('Module'): NativeType);
|
|
`;
|
|
|
|
const COMMANDS_WITH_COVERAGE_WRONG_FUNCTION = `
|
|
// @flow
|
|
|
|
const codegenNativeComponent = require('codegenNativeComponent');
|
|
import type {NativeComponentType} from 'codegenNativeComponent';
|
|
|
|
import type {ViewProps} from 'ViewPropTypes';
|
|
|
|
type ModuleProps = $ReadOnly<{|
|
|
...ViewProps,
|
|
|}>;
|
|
|
|
type NativeType = NativeComponentType<ModuleProps>;
|
|
|
|
// Coverage instrumentation of wrong function call - should fail
|
|
export const Commands = (cov_abcdef123().s[0]++, someOtherFunction({
|
|
supportedCommands: ['pause', 'play'],
|
|
}));
|
|
|
|
export default (codegenNativeComponent<ModuleProps>('Module'): NativeType);
|
|
`;
|
|
|
|
const COMMANDS_WITH_COMPLEX_COVERAGE_INVALID = `
|
|
// @flow
|
|
|
|
const codegenNativeComponent = require('codegenNativeComponent');
|
|
import type {NativeComponentType} from 'codegenNativeComponent';
|
|
|
|
import type {ViewProps} from 'ViewPropTypes';
|
|
|
|
type ModuleProps = $ReadOnly<{|
|
|
...ViewProps,
|
|
|}>;
|
|
|
|
type NativeType = NativeComponentType<ModuleProps>;
|
|
|
|
// Complex coverage instrumentation with invalid nested structure - should fail
|
|
export const Commands = (
|
|
cov_xyz789().f[1]++,
|
|
cov_xyz789().s[2]++,
|
|
{
|
|
pause: (ref) => {},
|
|
play: (ref) => {},
|
|
}
|
|
);
|
|
|
|
export default (codegenNativeComponent<ModuleProps>('Module'): NativeType);
|
|
`;
|
|
|
|
const COMMANDS_WITH_COVERAGE_WRONG_NAME = `
|
|
// @flow
|
|
|
|
const codegenNativeCommands = require('codegenNativeCommands');
|
|
const codegenNativeComponent = require('codegenNativeComponent');
|
|
import type {NativeComponentType} from 'codegenNativeComponent';
|
|
|
|
import type {ViewProps} from 'ViewPropTypes';
|
|
|
|
type ModuleProps = $ReadOnly<{|
|
|
...ViewProps,
|
|
|}>;
|
|
|
|
type NativeType = NativeComponentType<ModuleProps>;
|
|
|
|
interface NativeCommands {
|
|
+pause: (viewRef: React.ElementRef<NativeType>) => void;
|
|
+play: (viewRef: React.ElementRef<NativeType>) => void;
|
|
}
|
|
|
|
// Coverage instrumentation with correct function but wrong export name - should fail
|
|
export const WrongName = (cov_wrong123().s[0]++, codegenNativeCommands<NativeCommands>({
|
|
supportedCommands: ['pause', 'play'],
|
|
}));
|
|
|
|
export default (codegenNativeComponent<ModuleProps>('Module'): NativeType);
|
|
`;
|
|
|
|
const COMMANDS_WITH_COVERAGE_TYPE_CAST_INVALID = `
|
|
// @flow
|
|
|
|
const codegenNativeComponent = require('codegenNativeComponent');
|
|
import type {NativeComponentType} from 'codegenNativeComponent';
|
|
|
|
import type {ViewProps} from 'ViewPropTypes';
|
|
|
|
type ModuleProps = $ReadOnly<{|
|
|
...ViewProps,
|
|
|}>;
|
|
|
|
type NativeType = NativeComponentType<ModuleProps>;
|
|
|
|
interface NativeCommands {
|
|
+pause: (viewRef: React.ElementRef<NativeType>) => void;
|
|
+play: (viewRef: React.ElementRef<NativeType>) => void;
|
|
}
|
|
|
|
// Coverage instrumentation with type cast but wrong function - should fail
|
|
export const Commands: NativeCommands = (cov_cast123().s[0]++, invalidFunction({
|
|
supportedCommands: ['pause', 'play'],
|
|
}));
|
|
|
|
export default (codegenNativeComponent<ModuleProps>('Module'): NativeType);
|
|
`;
|
|
|
|
module.exports = {
|
|
'CommandsExportedWithDifferentNameNativeComponent.js':
|
|
COMMANDS_EXPORTED_WITH_DIFFERENT_NAME,
|
|
'CommandsExportedWithShorthandNativeComponent.js':
|
|
COMMANDS_EXPORTED_WITH_SHORTHAND,
|
|
'OtherCommandsExportNativeComponent.js': OTHER_COMMANDS_EXPORT,
|
|
'CommandsWithCoverageInvalidNativeComponent.js':
|
|
COMMANDS_WITH_COVERAGE_INVALID,
|
|
'CommandsWithCoverageWrongFunctionNativeComponent.js':
|
|
COMMANDS_WITH_COVERAGE_WRONG_FUNCTION,
|
|
'CommandsWithComplexCoverageInvalidNativeComponent.js':
|
|
COMMANDS_WITH_COMPLEX_COVERAGE_INVALID,
|
|
'CommandsWithCoverageWrongNameNativeComponent.js':
|
|
COMMANDS_WITH_COVERAGE_WRONG_NAME,
|
|
'CommandsWithCoverageTypeCastInvalidNativeComponent.js':
|
|
COMMANDS_WITH_COVERAGE_TYPE_CAST_INVALID,
|
|
};
|