add 'as const' annotations in preparation of natural inference rollout in xplat/js [4/n] (#51488)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51488

The Flow team is improving the way Flow infers type for primitive literals.
Announcement: https://fb.workplace.com/groups/flowlang/permalink/1725180268087629/

This diff prepares the codebase for the new behavior by codemoding `as const` annotations.

## Repro steps

1/ Used steps in D73610163 to produce the code changes.

2/ Reverted files where `flow` errored:
```
flow status --show-all-errors > errors.log
node ~/fbsource/fbcode/flow/facebook/error-analyzer.js errors.log |
  awk -F':' '{ print $1 }' | sort -u | grep -v 'Total Error Count' |
  xargs hg revert --rev .
```

3/ Reverted files that did not improve error count in new Flow mode
```
# Run Flow before change
~/fbsource/fbcode/flow/facebook/flowd status --show-all-errors > errors-0.log
# Run Flow after change
~/fbsource/fbcode/flow/facebook/flowd status --show-all-errors > errors-1.log

# Compute error counts before and after
node ~/fbsource/fbcode/flow/facebook/error-analyzer.js errors-0.log | sort > errors-counts-0.log
node ~/fbsource/fbcode/flow/facebook/error-analyzer.js errors-1.log | sort > errors-counts-1.log

# Revert files with no change in error count
comm -12 errors-counts-0.log errors-counts-1.log | awk -F':' '{ print $1 }' | xargs hg revert --rev .~1
```

## Note to code owners

Due to the large number of errors involved in this rollout, adding `as const` was the most feasible large-scale automated solution. Ideally, a lot of these errors would be fixed by adding other appropriate type annotations. For example instead of annotating
```
type Shape = {type: 'circle', radius: number} | {type: 'square', side: number} | ...;
type ShapeKind = 'circle' | 'square' | 'triangle';
const circle = {
  type: "circle" as const,  // <-- annotation added here
  radius: 42,
};
shape.type as ShapeKind;
takesShape(circle);
```
a more appropriate annotation would be
```
const circle: Circle = { type: "circle"; radius: 42 };
...
```

Changelog: [Internal]

drop-conflicts

Reviewed By: SamChou19815

Differential Revision: D75114154

fbshipit-source-id: 67ee5673816da9625431e2a2466a1e0038386151
This commit is contained in:
Panos Vekris
2025-05-21 08:42:30 -07:00
committed by Facebook GitHub Bot
parent 380dd59e80
commit 2a068c6559
16 changed files with 90 additions and 81 deletions
+1 -1
View File
@@ -190,7 +190,7 @@ const transform /*: BabelTransformer['transform'] */ = ({
try {
const babelConfig = {
// ES modules require sourceType='module' but OSS may not always want that
sourceType: 'unambiguous',
sourceType: 'unambiguous' as const,
...buildBabelConfig(filename, options, plugins),
caller: {name: 'metro', bundler: 'metro', platform: options.platform},
ast: true,
@@ -977,14 +977,14 @@ function buildNativeModulesDiff(
);
const newType = {
type: 'ObjectTypeAnnotation',
type: 'ObjectTypeAnnotation' as const,
properties: [
...newerNativeModule.spec.methods,
...newerNativeModule.spec.eventEmitters,
],
};
const oldType = {
type: 'ObjectTypeAnnotation',
type: 'ObjectTypeAnnotation' as const,
properties: [
...olderNativeModule.spec.methods,
...olderNativeModule.spec.eventEmitters,
@@ -1074,13 +1074,13 @@ function buildNativeComponentsDiff(
);
const newCommands = {
type: 'ObjectTypeAnnotation',
type: 'ObjectTypeAnnotation' as const,
properties: [command],
};
const oldCommands =
oldCommand != null
? {
type: 'ObjectTypeAnnotation',
type: 'ObjectTypeAnnotation' as const,
properties: [oldCommand],
}
: null;
@@ -1124,7 +1124,7 @@ function buildNativeComponentsDiff(
// We have to do this to remove the .defaults from the props and get it into
// standard JavaScript shapes.
const newConvertedProps = {
type: 'ObjectTypeAnnotation',
type: 'ObjectTypeAnnotation' as const,
properties: newerComponent.props.map(prop => ({
name: prop.name,
optional: prop.optional,
@@ -1132,7 +1132,7 @@ function buildNativeComponentsDiff(
})),
};
const oldConvertedProps = {
type: 'ObjectTypeAnnotation',
type: 'ObjectTypeAnnotation' as const,
properties: olderComponent.props.map(prop => ({
name: prop.name,
optional: prop.optional,
+2 -2
View File
@@ -74,8 +74,8 @@ async function processRNTesterCommandResult(
parsed = JSON.parse(line);
} catch {
parsed = {
type: 'console-log',
level: 'info',
type: 'console-log' as const,
level: 'info' as const,
message: line,
};
}
+2 -2
View File
@@ -331,10 +331,10 @@ function runSpec(spec: Spec): TestCaseResult {
spec.implementation();
invokeHooks(spec.parentContext, 'afterEachHooks');
status = 'passed';
status = 'passed' as const;
} catch (e) {
error = e;
status = 'failed';
status = 'failed' as const;
}
result.status = status;
+1 -1
View File
@@ -279,7 +279,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
: this.props.backdropColor ?? 'white',
};
let animationType = this.props.animationType || 'none';
let animationType = this.props.animationType || ('none' as const);
let presentationStyle = this.props.presentationStyle;
if (!presentationStyle) {
@@ -40,11 +40,11 @@ test('passes for identical configs', () => {
},
uiViewClassName: 'RCTView',
validAttributes: {
collapsable: true,
nativeID: true,
collapsable: true as const,
nativeID: true as const,
style: {
height: true,
width: true,
height: true as const,
width: true as const,
},
},
};
@@ -70,11 +70,11 @@ test('passes for identical configs', () => {
},
uiViewClassName: 'RCTView',
validAttributes: {
collapsable: true,
nativeID: true,
collapsable: true as const,
nativeID: true as const,
style: {
height: true,
width: true,
height: true as const,
width: true as const,
},
},
};
@@ -190,11 +190,11 @@ test('allows static viewconfigs to have more properties than native viewconfigs'
const staticViewConfig = {
uiViewClassName: 'RCTView',
validAttributes: {
collapsable: true,
nativeID: true,
collapsable: true as const,
nativeID: true as const,
style: {
height: true,
width: true,
height: true as const,
width: true as const,
},
},
};
+4 -4
View File
@@ -541,10 +541,10 @@ const userSelectToSelectableMap = {
};
const verticalAlignToTextAlignVerticalMap = {
auto: 'auto',
top: 'top',
bottom: 'bottom',
middle: 'center',
auto: 'auto' as const,
top: 'top' as const,
bottom: 'bottom' as const,
middle: 'center' as const,
};
export default TextImpl;
+20 -20
View File
@@ -30,23 +30,23 @@ export type NativeTextProps = $ReadOnly<{
const textViewConfig = {
validAttributes: {
isHighlighted: true,
isPressable: true,
numberOfLines: true,
ellipsizeMode: true,
allowFontScaling: true,
dynamicTypeRamp: true,
maxFontSizeMultiplier: true,
disabled: true,
selectable: true,
selectionColor: true,
adjustsFontSizeToFit: true,
minimumFontScale: true,
textBreakStrategy: true,
onTextLayout: true,
dataDetectorType: true,
android_hyphenationFrequency: true,
lineBreakStrategyIOS: true,
isHighlighted: true as const,
isPressable: true as const,
numberOfLines: true as const,
ellipsizeMode: true as const,
allowFontScaling: true as const,
dynamicTypeRamp: true as const,
maxFontSizeMultiplier: true as const,
disabled: true as const,
selectable: true as const,
selectionColor: true as const,
adjustsFontSizeToFit: true as const,
minimumFontScale: true as const,
textBreakStrategy: true as const,
onTextLayout: true as const,
dataDetectorType: true as const,
android_hyphenationFrequency: true as const,
lineBreakStrategyIOS: true as const,
},
directEventTypes: {
topTextLayout: {
@@ -58,9 +58,9 @@ const textViewConfig = {
const virtualTextViewConfig = {
validAttributes: {
isHighlighted: true,
isPressable: true,
maxFontSizeMultiplier: true,
isHighlighted: true as const,
isPressable: true as const,
maxFontSizeMultiplier: true as const,
},
uiViewClassName: 'RCTVirtualText',
};
+4 -3
View File
@@ -97,9 +97,10 @@ type HermesEngineSourceType =
*/
const HermesEngineSourceTypes = {
LOCAL_PREBUILT_TARBALL: 'local_prebuilt_tarball',
DOWNLOAD_PREBUILD_TARBALL: 'download_prebuild_tarball',
DOWNLOAD_PREBUILT_NIGHTLY_TARBALL: 'download_prebuilt_nightly_tarball',
LOCAL_PREBUILT_TARBALL: 'local_prebuilt_tarball' as const,
DOWNLOAD_PREBUILD_TARBALL: 'download_prebuild_tarball' as const,
DOWNLOAD_PREBUILT_NIGHTLY_TARBALL:
'download_prebuilt_nightly_tarball' as const,
};
/**
@@ -20,10 +20,10 @@ describe('createCompositeKeyForProps', () => {
it('excludes non-array and non-object allowlisted props', () => {
const props = {string: 'abc', number: 123, boolean: true, function() {}};
const allowlist = {
string: true,
number: true,
boolean: true,
function: true,
string: true as const,
number: true as const,
boolean: true as const,
function: true as const,
};
const compositeKey = createCompositeKeyForProps(props, allowlist);
@@ -53,7 +53,7 @@ describe('createCompositeKeyForProps', () => {
bar: new AnimatedEvent([], {useNativeDriver: true}),
};
const allowlist = {
bar: true,
bar: true as const,
};
const compositeKey = createCompositeKeyForProps(props, allowlist);
@@ -70,7 +70,7 @@ describe('createCompositeKeyForProps', () => {
};
const allowlist = {
style: {
baz: true,
baz: true as const,
},
};
const compositeKey = createCompositeKeyForProps(props, allowlist);
@@ -84,7 +84,7 @@ describe('createCompositeKeyForProps', () => {
bar: new AnimatedValue(1),
};
const allowlist = {
bar: true,
bar: true as const,
};
const compositeKey = createCompositeKeyForProps(props, allowlist);
@@ -108,7 +108,7 @@ describe('createCompositeKeyForProps', () => {
const props = {
style: {opacity, transform: [{rotateX: 1}, {rotateY}, {rotateZ: 1}]},
};
const allowlist = {style: {transform: true}};
const allowlist = {style: {transform: true as const}};
const compositeKey = createCompositeKeyForProps(props, allowlist);
expect(compositeKey).toEqual({
@@ -126,7 +126,7 @@ describe('createCompositeKeyForProps', () => {
const props = {
style: [{opacity: opacityA}, {opacity: opacityB}],
};
const allowlist = {style: {opacity: true}};
const allowlist = {style: {opacity: true as const}};
const compositeKey = createCompositeKeyForProps(props, allowlist);
expect(compositeKey).toEqual({style: {opacity: opacityB}});
@@ -240,7 +240,7 @@ const PromptOptions = () => {
},
{
text: 'Custom Cancel',
style: 'cancel',
style: 'cancel' as const,
},
];
@@ -397,7 +397,7 @@ export const examples = [
},
{
title: 'Alert with many Buttons',
platform: 'ios',
platform: 'ios' as const,
description: 'It can be used when more than three actions are required.',
render(): React.Node {
return <AlertWithManyButtons />;
@@ -405,7 +405,7 @@ export const examples = [
},
{
title: 'Alert with cancelable={true}',
platform: 'android',
platform: 'android' as const,
description:
'By passing cancelable={false} prop to alerts on Android, they can be dismissed by tapping outside of the alert box.',
render(): React.Node {
@@ -414,7 +414,7 @@ export const examples = [
},
{
title: 'Alert with styles',
platform: 'ios',
platform: 'ios' as const,
description:
"Alert buttons can be styled. There are three button styles - 'default' | 'cancel' | 'destructive'.",
render(): React.Node {
@@ -423,7 +423,7 @@ export const examples = [
},
{
title: 'Alert with styles + preferred',
platform: 'ios',
platform: 'ios' as const,
description:
"Alert buttons with 'isPreferred' will be emphasized, even over cancel buttons",
render(): React.Node {
@@ -432,14 +432,14 @@ export const examples = [
},
{
title: 'Prompt Options',
platform: 'ios',
platform: 'ios' as const,
render(): React.Node {
return <PromptOptions />;
},
},
{
title: 'Prompt Types',
platform: 'ios',
platform: 'ios' as const,
render(): React.Node {
return <PromptTypes />;
},
@@ -47,7 +47,7 @@ type CircleBlockProps = $ReadOnly<{
function CircleBlock({children, style}: CircleBlockProps): React.Node {
const circleStyle = {
flexDirection: 'row',
flexDirection: 'row' as const,
backgroundColor: '#f6f7f8',
borderWidth: 0.5,
borderColor: '#d6d7da',
@@ -428,7 +428,7 @@ const examples = [
title: 'Pressable with Ripple and Animated child',
description:
('Pressable can have an AnimatedComponent as a direct child.': string),
platform: 'android',
platform: 'android' as const,
render: function (): React.Node {
const mScale = new Animated.Value(1);
Animated.timing(mScale, {
@@ -455,10 +455,10 @@ const examples = [
title: 'Pressable with custom Ripple',
description:
("Pressable can specify ripple's radius, color and borderless params": string),
platform: 'android',
platform: 'android' as const,
render: function (): React.Node {
const nativeFeedbackButton = {
textAlign: 'center',
textAlign: 'center' as const,
margin: 10,
};
return (
@@ -543,7 +543,7 @@ const examples = [
render: function (): React.Node {
return <ForceTouchExample />;
},
platform: 'ios',
platform: 'ios' as const,
},
{
title: 'Pressable Hit Slop',
@@ -1117,7 +1117,11 @@ function TextBaseLineLayoutExample(props: {}): React.Node {
const marker = (
<View style={{width: 20, height: 20, backgroundColor: 'gray'}} />
);
const subtitleStyle = {fontSize: 16, marginTop: 8, fontWeight: 'bold'};
const subtitleStyle = {
fontSize: 16,
marginTop: 8,
fontWeight: 'bold' as const,
};
return (
<View>
@@ -296,7 +296,11 @@ class TextBaseLineLayoutExample extends React.Component<{}, mixed> {
const marker = (
<View style={{width: 20, height: 20, backgroundColor: 'gray'}} />
);
const subtitleStyle = {fontSize: 16, marginTop: 8, fontWeight: 'bold'};
const subtitleStyle = {
fontSize: 16,
marginTop: 8,
fontWeight: 'bold' as const,
};
return (
<View>
@@ -1475,7 +1479,7 @@ const examples = [
{
title: 'Dynamic Type (iOS only)',
render: function (): React.Node {
const boldStyle = {fontWeight: 'bold'};
const boldStyle = {fontWeight: 'bold' as const};
const boxStyle = {
borderWidth: 1,
padding: 8,
@@ -19,8 +19,8 @@ import type {
import RNTesterList from './RNTesterList';
export const Screens = {
COMPONENTS: 'components',
APIS: 'apis',
COMPONENTS: 'components' as const,
APIS: 'apis' as const,
PLAYGROUNDS: 'playgrounds',
};
@@ -106,14 +106,14 @@ export const getExamplesListWithRecentlyUsed = ({
],
[Screens.APIS]: [
{
key: 'RECENT_APIS',
key: 'RECENT_APIS' as const,
data: recentlyUsedAPIs,
title: 'Recently viewed',
title: 'Recently viewed' as const,
},
{
key: 'APIS',
key: 'APIS' as const,
data: apis.sort((a, b) => a.module.title.localeCompare(b.module.title)),
title: 'APIs',
title: 'APIs' as const,
},
],
};