Added support for method argument nullability
Summary: This diff adds support for enforcing nullability in the arguments for exported methods. We previously supported use of the nullable/nonnull attributes on method arguments, but didn't do anything to ensure that they were respected. Now, if an argument is marked as nonnull, and a null value is sent for that argument, it will display a redbox. In future, nonnull will be assumed by default, but for now we assume that un-annotated arguments can be null (to avoid breaking existing code).
This commit is contained in:
parent
e0ea046092
commit
95b9dd3a88
|
@ -15,13 +15,14 @@
|
|||
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
|
||||
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.m */; };
|
||||
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; };
|
||||
1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; };
|
||||
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
|
||||
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; };
|
||||
13DF61B61B67A45000EDB188 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */; };
|
||||
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */; };
|
||||
141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 141FC1201B222EBB004D5FFB /* IntegrationTests.m */; };
|
||||
143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; };
|
||||
144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClipRectTests.m */; };
|
||||
|
@ -167,6 +168,7 @@
|
|||
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
|
||||
138D6A151B53CD440074A87E /* RCTCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCacheTests.m; sourceTree = "<group>"; };
|
||||
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; };
|
||||
1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
|
||||
139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
|
||||
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = UIExplorer/AppDelegate.h; sourceTree = "<group>"; };
|
||||
|
@ -177,7 +179,7 @@
|
|||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = "<group>"; };
|
||||
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
|
||||
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = "<group>"; };
|
||||
13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
|
||||
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMethodArgumentTests.m; sourceTree = "<group>"; };
|
||||
141FC1201B222EBB004D5FFB /* IntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationTests.m; sourceTree = "<group>"; };
|
||||
143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = "<group>"; };
|
||||
|
@ -362,7 +364,8 @@
|
|||
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
|
||||
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
|
||||
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
|
||||
13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */,
|
||||
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */,
|
||||
1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */,
|
||||
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */,
|
||||
1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */,
|
||||
1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
|
||||
|
@ -791,6 +794,7 @@
|
|||
files = (
|
||||
1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */,
|
||||
144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */,
|
||||
1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */,
|
||||
1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */,
|
||||
1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */,
|
||||
1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */,
|
||||
|
@ -801,7 +805,7 @@
|
|||
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */,
|
||||
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
|
||||
1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
|
||||
13DF61B61B67A45000EDB188 /* RCTModuleMethodTests.m in Sources */,
|
||||
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */,
|
||||
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "RCTModuleMethod.h"
|
||||
|
||||
@interface RCTMethodArgumentTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTMethodArgumentTests
|
||||
|
||||
extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes);
|
||||
|
||||
- (void)testOneArgument
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo:(NSInteger)foo";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo:");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)1);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
|
||||
}
|
||||
|
||||
- (void)testTwoArguments
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testSpaces
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testNewlines
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testUnnamedArgs
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo::");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testUntypedUnnamedArgs
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo:foo:bar:bar";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo:::");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)3);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"id");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"id");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[2]).type, @"id");
|
||||
}
|
||||
|
||||
- (void)testAttributes
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testNullability
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:baz:");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)3);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[2]).type, @"id");
|
||||
XCTAssertEqual(((RCTMethodArgument *)arguments[0]).nullability, RCTNullable);
|
||||
XCTAssertEqual(((RCTMethodArgument *)arguments[1]).nullability, RCTNonnullable);
|
||||
XCTAssertEqual(((RCTMethodArgument *)arguments[2]).nullability, RCTNullabilityUnspecified);
|
||||
}
|
||||
|
||||
- (void)testSemicolonStripping
|
||||
{
|
||||
NSArray *arguments;
|
||||
NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;";
|
||||
RCTParseObjCMethodName(&methodName, &arguments);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(arguments.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
|
||||
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
|
||||
}
|
||||
|
||||
@end
|
|
@ -1,15 +1,22 @@
|
|||
//
|
||||
// RCTModuleMethodTests.m
|
||||
// UIExplorer
|
||||
//
|
||||
// Created by Nick Lockwood on 28/07/2015.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "RCTModuleMethod.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@interface RCTModuleMethodTests : XCTestCase
|
||||
|
||||
|
@ -17,94 +24,40 @@
|
|||
|
||||
@implementation RCTModuleMethodTests
|
||||
|
||||
extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes);
|
||||
- (void)doFooWithBar:(__unused NSString *)bar { }
|
||||
|
||||
- (void)testOneArgument
|
||||
- (void)testNonnull
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(NSInteger)foo";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)1);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
}
|
||||
NSString *methodName = @"doFooWithBar:(nonnull NSString *)bar";
|
||||
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
|
||||
JSMethodName:nil
|
||||
moduleClass:[self class]];
|
||||
|
||||
- (void)testTwoArguments
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
{
|
||||
__block BOOL loggedError = NO;
|
||||
RCTPerformBlockWithLogFunction(^{
|
||||
[method invokeWithBridge:nil module:self arguments:@[@"Hello World"]];
|
||||
}, ^(RCTLogLevel level,
|
||||
__unused NSString *fileName,
|
||||
__unused NSNumber *lineNumber,
|
||||
__unused NSString *message) {
|
||||
loggedError = (level == RCTLogLevelError);
|
||||
});
|
||||
XCTAssertFalse(loggedError);
|
||||
}
|
||||
|
||||
- (void)testSpaces
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testNewlines
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testUnnamedArgs
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo::");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testUntypedUnnamedArgs
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:foo:bar:bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:::");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)3);
|
||||
XCTAssertEqualObjects(argTypes[0], @"id");
|
||||
XCTAssertEqualObjects(argTypes[1], @"id");
|
||||
XCTAssertEqualObjects(argTypes[2], @"id");
|
||||
}
|
||||
|
||||
- (void)testAttributes
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSString");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
}
|
||||
|
||||
- (void)testSemicolonStripping
|
||||
{
|
||||
NSArray *argTypes;
|
||||
NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;";
|
||||
RCTParseObjCMethodName(&methodName, &argTypes);
|
||||
XCTAssertEqualObjects(methodName, @"foo:bar:");
|
||||
XCTAssertEqual(argTypes.count, (NSUInteger)2);
|
||||
XCTAssertEqualObjects(argTypes[0], @"NSString");
|
||||
XCTAssertEqualObjects(argTypes[1], @"BOOL");
|
||||
{
|
||||
__block BOOL loggedError = NO;
|
||||
RCTPerformBlockWithLogFunction(^{
|
||||
[method invokeWithBridge:nil module:self arguments:@[[NSNull null]]];
|
||||
}, ^(RCTLogLevel level,
|
||||
__unused NSString *fileName,
|
||||
__unused NSNumber *lineNumber,
|
||||
__unused NSString *message) {
|
||||
loggedError = (level == RCTLogLevelError);
|
||||
});
|
||||
XCTAssertTrue(loggedError);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -60,7 +60,7 @@ RCT_EXTERN NSString *RCTFormatLog(
|
|||
extern RCTLogFunction RCTDefaultLogFunction;
|
||||
|
||||
/**
|
||||
* These methods get and set the current logging threshold. This is the level
|
||||
* These methods get and set the global logging threshold. This is the level
|
||||
* below which logs will be ignored. Default is RCTLogLevelInfo for debug and
|
||||
* RCTLogLevelError for production.
|
||||
*/
|
||||
|
@ -68,7 +68,7 @@ RCT_EXTERN void RCTSetLogThreshold(RCTLogLevel threshold);
|
|||
RCT_EXTERN RCTLogLevel RCTGetLogThreshold(void);
|
||||
|
||||
/**
|
||||
* These methods get and set the current logging function called by the RCTLogXX
|
||||
* These methods get and set the global logging function called by the RCTLogXX
|
||||
* macros. You can use these to replace the standard behavior with custom log
|
||||
* functionality.
|
||||
*/
|
||||
|
@ -82,6 +82,13 @@ RCT_EXTERN RCTLogFunction RCTGetLogFunction(void);
|
|||
*/
|
||||
RCT_EXTERN void RCTAddLogFunction(RCTLogFunction logFunction);
|
||||
|
||||
/**
|
||||
* This method temporarily overrides the log function while performing the
|
||||
* specified block. This is useful for testing purposes (to detect if a given
|
||||
* function logs something) or to suppress or override logging temporarily.
|
||||
*/
|
||||
RCT_EXTERN void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunction);
|
||||
|
||||
/**
|
||||
* This method adds a conditional prefix to any messages logged within the scope
|
||||
* of the passed block. This is useful for adding additional context to log
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
@end
|
||||
|
||||
static NSString *const RCTLogPrefixStack = @"RCTLogPrefixStack";
|
||||
static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack";
|
||||
|
||||
const char *RCTLogLevels[] = {
|
||||
"info",
|
||||
|
@ -107,17 +107,42 @@ void RCTAddLogFunction(RCTLogFunction logFunction)
|
|||
}
|
||||
}
|
||||
|
||||
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
|
||||
/**
|
||||
* returns the topmost stacked log function for the current thread, which
|
||||
* may not be the same as the current value of RCTCurrentLogFunction.
|
||||
*/
|
||||
static RCTLogFunction RCTGetLocalLogFunction()
|
||||
{
|
||||
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
|
||||
NSMutableArray *prefixStack = threadDictionary[RCTLogPrefixStack];
|
||||
if (!prefixStack) {
|
||||
prefixStack = [[NSMutableArray alloc] init];
|
||||
threadDictionary[RCTLogPrefixStack] = prefixStack;
|
||||
NSArray *functionStack = threadDictionary[RCTLogFunctionStack];
|
||||
RCTLogFunction logFunction = [functionStack lastObject];
|
||||
if (logFunction) {
|
||||
return logFunction;
|
||||
}
|
||||
[prefixStack addObject:prefix];
|
||||
return RCTCurrentLogFunction;
|
||||
}
|
||||
|
||||
void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunction)
|
||||
{
|
||||
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
|
||||
NSMutableArray *functionStack = threadDictionary[RCTLogFunctionStack];
|
||||
if (!functionStack) {
|
||||
functionStack = [[NSMutableArray alloc] init];
|
||||
threadDictionary[RCTLogFunctionStack] = functionStack;
|
||||
}
|
||||
[functionStack addObject:logFunction];
|
||||
block();
|
||||
[prefixStack removeLastObject];
|
||||
[functionStack removeLastObject];
|
||||
}
|
||||
|
||||
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
|
||||
{
|
||||
RCTLogFunction logFunction = RCTGetLocalLogFunction();
|
||||
if (logFunction) {
|
||||
RCTPerformBlockWithLogFunction(block, ^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
||||
logFunction(level, fileName, lineNumber, [prefix stringByAppendingString:message]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
NSString *RCTFormatLog(
|
||||
|
@ -165,8 +190,8 @@ void _RCTLogFormat(
|
|||
int lineNumber,
|
||||
NSString *format, ...)
|
||||
{
|
||||
|
||||
BOOL log = RCT_DEBUG || (RCTCurrentLogFunction != nil);
|
||||
RCTLogFunction logFunction = RCTGetLocalLogFunction();
|
||||
BOOL log = RCT_DEBUG || (logFunction != nil);
|
||||
if (log && level >= RCTCurrentLogThreshold) {
|
||||
|
||||
// Get message
|
||||
|
@ -175,18 +200,10 @@ void _RCTLogFormat(
|
|||
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
va_end(args);
|
||||
|
||||
// Add prefix
|
||||
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
|
||||
NSArray *prefixStack = threadDictionary[RCTLogPrefixStack];
|
||||
NSString *prefix = [prefixStack lastObject];
|
||||
if (prefix) {
|
||||
message = [prefix stringByAppendingString:message];
|
||||
}
|
||||
|
||||
// Call log function
|
||||
RCTCurrentLogFunction(
|
||||
level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message
|
||||
);
|
||||
if (logFunction) {
|
||||
logFunction(level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message);
|
||||
}
|
||||
|
||||
#if RCT_DEBUG // Red box is only available in debug mode
|
||||
|
||||
|
|
|
@ -16,12 +16,25 @@ typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) {
|
|||
RCTJavaScriptFunctionKindAsync,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTNullability) {
|
||||
RCTNullabilityUnspecified,
|
||||
RCTNullable,
|
||||
RCTNonnullable,
|
||||
};
|
||||
|
||||
@interface RCTMethodArgument : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *type;
|
||||
@property (nonatomic, readonly) RCTNullability nullability;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTModuleMethod : NSObject
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *JSMethodName;
|
||||
@property (nonatomic, strong, readonly) Class moduleClass;
|
||||
@property (nonatomic, assign, readonly) SEL selector;
|
||||
@property (nonatomic, assign, readonly) RCTJavaScriptFunctionKind functionKind;
|
||||
@property (nonatomic, readonly) Class moduleClass;
|
||||
@property (nonatomic, readonly) SEL selector;
|
||||
@property (nonatomic, readonly) RCTJavaScriptFunctionKind functionKind;
|
||||
|
||||
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
|
|
|
@ -17,6 +17,22 @@
|
|||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
typedef void (^RCTArgumentBlock)(RCTBridge *, NSInvocation *, NSUInteger, id);
|
||||
|
||||
@implementation RCTMethodArgument
|
||||
|
||||
- (instancetype)initWithType:(NSString *)type
|
||||
nullability:(RCTNullability)nullability
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_type = [type copy];
|
||||
_nullability = nullability;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBridge (RCTModuleMethod)
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module
|
||||
|
@ -36,16 +52,17 @@
|
|||
RCT_NOT_IMPLEMENTED(-init)
|
||||
|
||||
void RCTParseObjCMethodName(NSString **, NSArray **);
|
||||
void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
|
||||
void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments)
|
||||
{
|
||||
static NSRegularExpression *typeNameRegex;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))";
|
||||
NSString *constPattern = @"(?:const)";
|
||||
NSString *nullabilityPattern = @"(?:__nullable|__nonnull|nullable|nonnull|__attribute__\\(\\(nonnull\\)\\)|__attribute__\\(\\(nullable\\)\\))";
|
||||
NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:%@|%@|%@)\\s*)",
|
||||
unusedPattern, constPattern, nullabilityPattern];
|
||||
NSString *nullablePattern = @"(?:__nullable|nullable|__attribute__\\(\\(nullable\\)\\))";
|
||||
NSString *nonnullPattern = @"(?:__nonnull|nonnull|__attribute__\\(\\(nonnull\\)\\))";
|
||||
NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:%@|%@|(%@)|(%@))\\s*)",
|
||||
unusedPattern, constPattern, nullablePattern, nonnullPattern];
|
||||
NSString *pattern = [NSString stringWithFormat:@"(?<=:)(\\s*\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\))?\\s*\\w+",
|
||||
annotationPattern];
|
||||
typeNameRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
|
||||
|
@ -54,12 +71,15 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
|
|||
// Extract argument types
|
||||
NSString *methodName = *objCMethodName;
|
||||
NSRange methodRange = {0, methodName.length};
|
||||
NSMutableArray *arguments = [NSMutableArray array];
|
||||
NSMutableArray *args = [NSMutableArray array];
|
||||
[typeNameRegex enumerateMatchesInString:methodName options:0 range:methodRange usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
|
||||
NSRange range = [result rangeAtIndex:2];
|
||||
[arguments addObject:range.length ? [methodName substringWithRange:range] : @"id"];
|
||||
NSRange typeRange = [result rangeAtIndex:4];
|
||||
NSString *type = typeRange.length ? [methodName substringWithRange:typeRange] : @"id";
|
||||
RCTNullability nullability = [result rangeAtIndex:2].length ? RCTNullable :
|
||||
[result rangeAtIndex:3].length ? RCTNonnullable : RCTNullabilityUnspecified;
|
||||
[args addObject:[[RCTMethodArgument alloc] initWithType:type nullability:nullability]];
|
||||
}];
|
||||
*argTypes = [arguments copy];
|
||||
*arguments = [args copy];
|
||||
|
||||
// Remove the parameter types and names
|
||||
methodName = [typeNameRegex stringByReplacingMatchesInString:methodName options:0
|
||||
|
@ -84,8 +104,8 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
|
|||
{
|
||||
if ((self = [super init])) {
|
||||
|
||||
NSArray *argTypes;
|
||||
RCTParseObjCMethodName(&objCMethodName, &argTypes);
|
||||
NSArray *arguments;
|
||||
RCTParseObjCMethodName(&objCMethodName, &arguments);
|
||||
|
||||
_moduleClass = moduleClass;
|
||||
_selector = NSSelectorFromString(objCMethodName);
|
||||
|
@ -112,7 +132,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
|
|||
#define RCT_ARG_BLOCK(_logic) \
|
||||
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
|
||||
_logic \
|
||||
[invocation setArgument:&value atIndex:index]; \
|
||||
[invocation setArgument:&value atIndex:(index) + 2]; \
|
||||
}];
|
||||
|
||||
void (^addBlockArgument)(void) = ^{
|
||||
|
@ -143,12 +163,13 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
|
|||
};
|
||||
|
||||
for (NSUInteger i = 2; i < numberOfArguments; i++) {
|
||||
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
|
||||
const char *objcType = [_methodSignature getArgumentTypeAtIndex:i];
|
||||
|
||||
NSString *selName = argTypes[i - 2];
|
||||
SEL selector = NSSelectorFromString([selName stringByAppendingString:@":"]);
|
||||
RCTMethodArgument *argument = arguments[i - 2];
|
||||
NSString *typeName = argument.type;
|
||||
SEL selector = NSSelectorFromString([typeName stringByAppendingString:@":"]);
|
||||
if ([RCTConvert respondsToSelector:selector]) {
|
||||
switch (argumentType[0]) {
|
||||
switch (objcType[0]) {
|
||||
|
||||
#define RCT_CONVERT_CASE(_value, _type) \
|
||||
case _value: { \
|
||||
|
@ -157,109 +178,125 @@ case _value: { \
|
|||
break; \
|
||||
}
|
||||
|
||||
RCT_CONVERT_CASE(':', SEL)
|
||||
RCT_CONVERT_CASE('*', const char *)
|
||||
RCT_CONVERT_CASE('c', char)
|
||||
RCT_CONVERT_CASE('C', unsigned char)
|
||||
RCT_CONVERT_CASE('s', short)
|
||||
RCT_CONVERT_CASE('S', unsigned short)
|
||||
RCT_CONVERT_CASE('i', int)
|
||||
RCT_CONVERT_CASE('I', unsigned int)
|
||||
RCT_CONVERT_CASE('l', long)
|
||||
RCT_CONVERT_CASE('L', unsigned long)
|
||||
RCT_CONVERT_CASE('q', long long)
|
||||
RCT_CONVERT_CASE('Q', unsigned long long)
|
||||
RCT_CONVERT_CASE('f', float)
|
||||
RCT_CONVERT_CASE('d', double)
|
||||
RCT_CONVERT_CASE('B', BOOL)
|
||||
RCT_CONVERT_CASE('@', id)
|
||||
RCT_CONVERT_CASE('^', void *)
|
||||
RCT_CONVERT_CASE(':', SEL)
|
||||
RCT_CONVERT_CASE('*', const char *)
|
||||
RCT_CONVERT_CASE('c', char)
|
||||
RCT_CONVERT_CASE('C', unsigned char)
|
||||
RCT_CONVERT_CASE('s', short)
|
||||
RCT_CONVERT_CASE('S', unsigned short)
|
||||
RCT_CONVERT_CASE('i', int)
|
||||
RCT_CONVERT_CASE('I', unsigned int)
|
||||
RCT_CONVERT_CASE('l', long)
|
||||
RCT_CONVERT_CASE('L', unsigned long)
|
||||
RCT_CONVERT_CASE('q', long long)
|
||||
RCT_CONVERT_CASE('Q', unsigned long long)
|
||||
RCT_CONVERT_CASE('f', float)
|
||||
RCT_CONVERT_CASE('d', double)
|
||||
RCT_CONVERT_CASE('B', BOOL)
|
||||
RCT_CONVERT_CASE('@', id)
|
||||
RCT_CONVERT_CASE('^', void *)
|
||||
|
||||
case '{': {
|
||||
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) {
|
||||
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
|
||||
void *returnValue = malloc(methodSignature.methodReturnLength);
|
||||
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
[_invocation setTarget:[RCTConvert class]];
|
||||
[_invocation setSelector:selector];
|
||||
[_invocation setArgument:&json atIndex:2];
|
||||
[_invocation invoke];
|
||||
[_invocation getReturnValue:returnValue];
|
||||
case '{': {
|
||||
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) {
|
||||
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
|
||||
void *returnValue = malloc(methodSignature.methodReturnLength);
|
||||
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
[_invocation setTarget:[RCTConvert class]];
|
||||
[_invocation setSelector:selector];
|
||||
[_invocation setArgument:&json atIndex:2];
|
||||
[_invocation invoke];
|
||||
[_invocation getReturnValue:returnValue];
|
||||
|
||||
[invocation setArgument:returnValue atIndex:index];
|
||||
[invocation setArgument:returnValue atIndex:index + 2];
|
||||
|
||||
free(returnValue);
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
defaultCase(argumentType);
|
||||
free(returnValue);
|
||||
}];
|
||||
break;
|
||||
}
|
||||
} else if ([selName isEqualToString:@"RCTResponseSenderBlock"]) {
|
||||
addBlockArgument();
|
||||
} else if ([selName isEqualToString:@"RCTResponseErrorBlock"]) {
|
||||
RCT_ARG_BLOCK(
|
||||
|
||||
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing id value = (json ? ^(NSError *error) {
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, @[RCTJSErrorFromNSError(error)]]];
|
||||
} : ^(__unused NSError *error) {});
|
||||
)
|
||||
} else if ([selName isEqualToString:@"RCTPromiseResolveBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 2,
|
||||
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
|
||||
_moduleClass, objCMethodName);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing RCTPromiseResolveBlock value = (^(id result) {
|
||||
NSArray *arguments = result ? @[result] : @[];
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, arguments]];
|
||||
});
|
||||
)
|
||||
_functionKind = RCTJavaScriptFunctionKindAsync;
|
||||
} else if ([selName isEqualToString:@"RCTPromiseRejectBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 1,
|
||||
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
|
||||
_moduleClass, objCMethodName);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) {
|
||||
NSDictionary *errorJSON = RCTJSErrorFromNSError(error);
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, @[errorJSON]]];
|
||||
});
|
||||
)
|
||||
_functionKind = RCTJavaScriptFunctionKindAsync;
|
||||
} else {
|
||||
|
||||
// Unknown argument type
|
||||
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
|
||||
" to support this type.", selName, [self methodName]);
|
||||
default:
|
||||
defaultCase(objcType);
|
||||
}
|
||||
} else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) {
|
||||
addBlockArgument();
|
||||
} else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) {
|
||||
RCT_ARG_BLOCK(
|
||||
|
||||
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing id value = (json ? ^(NSError *error) {
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, @[RCTJSErrorFromNSError(error)]]];
|
||||
} : ^(__unused NSError *error) {});
|
||||
)
|
||||
} else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 2,
|
||||
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
|
||||
_moduleClass, objCMethodName);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing RCTPromiseResolveBlock value = (^(id result) {
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, result ? @[result] : @[]]];
|
||||
});
|
||||
)
|
||||
_functionKind = RCTJavaScriptFunctionKindAsync;
|
||||
} else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
|
||||
RCTAssert(i == numberOfArguments - 1,
|
||||
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
|
||||
_moduleClass, objCMethodName);
|
||||
RCT_ARG_BLOCK(
|
||||
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
|
||||
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index,
|
||||
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||
__autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) {
|
||||
NSDictionary *errorJSON = RCTJSErrorFromNSError(error);
|
||||
[bridge _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[json, @[errorJSON]]];
|
||||
});
|
||||
)
|
||||
_functionKind = RCTJavaScriptFunctionKindAsync;
|
||||
} else {
|
||||
|
||||
// Unknown argument type
|
||||
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
|
||||
" to support this type.", typeName, [self methodName]);
|
||||
}
|
||||
|
||||
if (RCT_DEBUG) {
|
||||
RCTNullability nullability = argument.nullability;
|
||||
if (nullability == RCTNonnullable) {
|
||||
RCTArgumentBlock oldBlock = argumentBlocks[i - 2];
|
||||
argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) {
|
||||
if (json == nil || json == (id)kCFNull) {
|
||||
RCTLogError(@"Argument %tu of %@.%@ must not be null", index,
|
||||
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||
id null = nil;
|
||||
[invocation setArgument:&null atIndex:index + 2];
|
||||
} else {
|
||||
oldBlock(bridge, invocation, index, json);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_argumentBlocks = [argumentBlocks copy];
|
||||
|
@ -305,8 +342,8 @@ case _value: { \
|
|||
NSUInteger index = 0;
|
||||
for (id json in arguments) {
|
||||
id arg = RCTNilIfNull(json);
|
||||
void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
|
||||
block(bridge, invocation, index + 2, arg);
|
||||
RCTArgumentBlock block = _argumentBlocks[index];
|
||||
block(bridge, invocation, index, arg);
|
||||
index++;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue