From 94995fdd9f278eb3858d4aabb4e63cdc3997017a Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Thu, 23 Oct 2025 06:42:55 -0700 Subject: [PATCH] add rules to prefer Error objects to be thrown and rejected (#54229) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/54229 Changelog: [General][Added] Added eslint rule to warn when a non-error is being thrown from a function or rejected for a promise. Reviewed By: huntie Differential Revision: D85237916 fbshipit-source-id: e0e4fbc6b4620a19be1959d3953856c7e44ad4e0 --- .eslintrc.js | 3 +++ .../Libraries/Debugging/DebuggingOverlayRegistry.js | 10 ++++++++-- .../NativeComponent/NativeComponentRegistryUnstable.js | 4 +++- packages/react-native/scripts/codegen/codegen-utils.js | 4 ++-- .../codegen/generate-artifacts-executor/utils.js | 4 ++-- .../NativeModuleExample/NativeScreenshotManager.js | 2 +- .../js/examples/ContentURLAndroid/ContentURLAndroid.js | 2 +- 7 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index a6d2c177a27..3f3ff7adda0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -35,6 +35,9 @@ module.exports = { // Flow handles these checks for us, so they aren't required 'no-undef': 'off', 'no-unreachable': 'off', + // Throwing from function or rejecting promises with non-error values could result in unclear error stack traces and lead to harder debugging + 'prefer-promise-reject-errors': 'error', + 'no-throw-literal': 'error', }, }, { diff --git a/packages/react-native/Libraries/Debugging/DebuggingOverlayRegistry.js b/packages/react-native/Libraries/Debugging/DebuggingOverlayRegistry.js index b8cba996142..e81cb950463 100644 --- a/packages/react-native/Libraries/Debugging/DebuggingOverlayRegistry.js +++ b/packages/react-native/Libraries/Debugging/DebuggingOverlayRegistry.js @@ -332,7 +332,9 @@ class DebuggingOverlayRegistry { instance.measure((x, y, width, height, left, top) => { // measure can execute callback without any values provided to signal error. if (left == null || top == null || width == null || height == null) { - reject('Unexpectedly failed to call measure on an instance.'); + reject( + new Error('Unexpectedly failed to call measure on an instance.'), + ); } resolve({ @@ -480,7 +482,11 @@ class DebuggingOverlayRegistry { width == null || height == null ) { - reject('Unexpectedly failed to call measure on an instance.'); + reject( + new Error( + 'Unexpectedly failed to call measure on an instance.', + ), + ); } resolve({x: left, y: top, width, height}); diff --git a/packages/react-native/Libraries/NativeComponent/NativeComponentRegistryUnstable.js b/packages/react-native/Libraries/NativeComponent/NativeComponentRegistryUnstable.js index cb6ac57f52e..183076b80dd 100644 --- a/packages/react-native/Libraries/NativeComponent/NativeComponentRegistryUnstable.js +++ b/packages/react-native/Libraries/NativeComponent/NativeComponentRegistryUnstable.js @@ -23,7 +23,9 @@ export function unstable_hasComponent(name: string): boolean { hasNativeComponent = global.__nativeComponentRegistry__hasComponent(name); componentNameToExists.set(name, hasNativeComponent); } else { - throw `unstable_hasComponent('${name}'): Global function is not registered`; + throw new Error( + `unstable_hasComponent('${name}'): Global function is not registered`, + ); } } return hasNativeComponent; diff --git a/packages/react-native/scripts/codegen/codegen-utils.js b/packages/react-native/scripts/codegen/codegen-utils.js index bcd6729f150..2fc48a1d022 100644 --- a/packages/react-native/scripts/codegen/codegen-utils.js +++ b/packages/react-native/scripts/codegen/codegen-utils.js @@ -30,7 +30,7 @@ function getCodegen() /*: $FlowFixMe */ { RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen.js'); } if (!RNCodegen) { - throw 'RNCodegen not found.'; + throw new Error('RNCodegen not found.'); } return RNCodegen; } @@ -45,7 +45,7 @@ function getCombineJSToSchema() /*: $FlowFixMe */ { combineJSToSchema = require('@react-native/codegen/lib/cli/combine/combine-js-to-schema.js'); } if (!combineJSToSchema) { - throw 'combine-js-to-schema not found.'; + throw new Error('combine-js-to-schema not found.'); } return combineJSToSchema; } diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js index 97a4b89c540..680f8a6d295 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js @@ -38,7 +38,7 @@ const codegenLog = (text /*: string */, info /*: boolean */ = false) => { function readPkgJsonInDirectory(dir /*: string */) /*: $FlowFixMe */ { const pkgJsonPath = path.join(dir, 'package.json'); if (!fs.existsSync(pkgJsonPath)) { - throw `[Codegen] Error: ${pkgJsonPath} does not exist.`; + throw new Error(`[Codegen] Error: ${pkgJsonPath} does not exist.`); } return JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); } @@ -174,7 +174,7 @@ function findProjectRootLibraries( } if (typeof pkgJson.codegenConfig !== 'object') { - throw 'The "codegenConfig" field must be an Object.'; + throw new Error('The "codegenConfig" field must be an Object.'); } return extractLibrariesFromJSON(pkgJson, projectRoot); diff --git a/packages/rn-tester/NativeModuleExample/NativeScreenshotManager.js b/packages/rn-tester/NativeModuleExample/NativeScreenshotManager.js index a04cca85a83..0f6b0682777 100644 --- a/packages/rn-tester/NativeModuleExample/NativeScreenshotManager.js +++ b/packages/rn-tester/NativeModuleExample/NativeScreenshotManager.js @@ -30,5 +30,5 @@ export function takeScreenshot( if (NativeModule != null) { return NativeModule.takeScreenshot(id, options); } - return Promise.reject(); + return Promise.reject(new Error('ScreenshotManager is not defined.')); } diff --git a/packages/rn-tester/js/examples/ContentURLAndroid/ContentURLAndroid.js b/packages/rn-tester/js/examples/ContentURLAndroid/ContentURLAndroid.js index 81f88a78bf1..69bce4cd022 100644 --- a/packages/rn-tester/js/examples/ContentURLAndroid/ContentURLAndroid.js +++ b/packages/rn-tester/js/examples/ContentURLAndroid/ContentURLAndroid.js @@ -34,7 +34,7 @@ function blobToBase64(blob: Blob) { if (typeof result === 'string') { resolve(result); } else { - reject('error: incompatible types'); + reject(new Error('error: incompatible types')); } }; reader.readAsDataURL(blob);