Skip to content

Commit 5ff746d

Browse files
committed
Add measure-ios command
1 parent b31b62e commit 5ff746d

File tree

13 files changed

+4397
-235
lines changed

13 files changed

+4397
-235
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ node_modules
33
.vscode
44
build/
55
*.tsbuildinfo
6+
yarn-error.log

babel.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
presets: [
3+
['@babel/preset-env', {targets: {node: '8'}}],
4+
'@babel/preset-typescript',
5+
],
6+
};

definitions/xcode/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module 'xcode' {
2+
const module: any;
3+
4+
export default module;
5+
}

definitions/xcode/lib/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module 'xcode/lib/pbxFile' {
2+
const module: any;
3+
4+
export default module;
5+
}

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@
1313
"repository": "https://github.com/Kudo/react-native-cli-plugin-benchmark",
1414
"license": "MIT",
1515
"devDependencies": {
16+
"@babel/preset-env": "^7.8.4",
17+
"@babel/preset-typescript": "^7.8.3",
1618
"@react-native-community/cli-platform-android": "^4.0.1",
1719
"@react-native-community/cli-platform-ios": "^4.0.1",
1820
"@react-native-community/cli-types": "^3.0.0",
1921
"@types/glob": "^7.1.1",
2022
"@types/graceful-fs": "^4.1.3",
23+
"@types/jest": "^25.1.3",
2124
"@types/lodash": "^4.14.149",
2225
"@types/node": "^13.7.1",
2326
"@types/node-fetch": "^2.5.4",
27+
"jest": "^25.1.0",
2428
"prettier": "^1.19.1",
2529
"typescript": "^3.7.5"
2630
},
@@ -31,6 +35,7 @@
3135
"execa": "^4.0.0",
3236
"glob": "^7.1.6",
3337
"graceful-fs": "^4.2.3",
34-
"lodash": "^4.17.15"
38+
"lodash": "^4.17.15",
39+
"xcode": "^2.1.0"
3540
}
3641
}

src/commands/common/ObjcPatcher.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @format
3+
*/
4+
5+
import fs from 'fs';
6+
7+
export default class ObjcPatcher {
8+
_content: string;
9+
_patchSig: string;
10+
11+
constructor(fromFile: string, patchSignature: string) {
12+
this._content = fs.readFileSync(fromFile, {encoding: 'utf8'});
13+
this._patchSig = `/* Patched by ObjcPatcher: ${patchSignature} */\n`;
14+
}
15+
16+
isPatched(): boolean {
17+
return this._content.indexOf(this._patchSig) >= 0;
18+
}
19+
20+
addImport(file: string) {
21+
const lastImportBegin = this._content.lastIndexOf('\n#import');
22+
let lastImportEnd = this._content.indexOf('\n', lastImportBegin + 1);
23+
const headPart = this._content.substring(0, lastImportEnd);
24+
const tailPart = this._content.substring(lastImportEnd);
25+
this._content = headPart + `\n#import ${file}` + tailPart;
26+
return this;
27+
}
28+
29+
addFunction(code: string) {
30+
const lastImplEnd = this._content.lastIndexOf('\n@end');
31+
const headPart = this._content.substring(0, lastImplEnd);
32+
const tailPart = this._content.substring(lastImplEnd);
33+
this._content = headPart + `\n${code}` + tailPart;
34+
return this;
35+
}
36+
37+
replace(searchValue: string | RegExp, replaceValue: string) {
38+
this._content = this._content.replace(searchValue, replaceValue);
39+
return this;
40+
}
41+
42+
write(toFile: string): void {
43+
this._addPatchSigIfNeeded();
44+
fs.writeFileSync(toFile, this._content);
45+
}
46+
47+
_addPatchSigIfNeeded() {
48+
if (this.isPatched()) {
49+
return this;
50+
}
51+
this._content = this._patchSig + this._content;
52+
return this;
53+
}
54+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* @format
3+
*/
4+
5+
import ObjcPatcher from '../ObjcPatcher';
6+
import fs from 'fs';
7+
8+
jest.mock('fs');
9+
10+
describe('ObjcPatcher', () => {
11+
test('addImport() will append imports', () => {
12+
const beforePatch = `\
13+
#import <UIKit/UIKit.h>
14+
#import <React/RCTRootView.h>
15+
16+
@implementation AppDelegate
17+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
18+
{
19+
return NO;
20+
}
21+
`;
22+
23+
const afterPatch = `\
24+
/* Patched by ObjcPatcher: foo */
25+
#import <UIKit/UIKit.h>
26+
#import <React/RCTRootView.h>
27+
#import <React/Foo.h>
28+
29+
@implementation AppDelegate
30+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
31+
{
32+
return NO;
33+
}
34+
`;
35+
fs.readFileSync = jest.fn().mockReturnValueOnce(beforePatch);
36+
37+
fs.writeFileSync = jest.fn();
38+
39+
const patcher = new ObjcPatcher('/foo', 'foo');
40+
patcher.addImport('<React/Foo.h>').write('/bar');
41+
expect(fs.writeFileSync.mock.calls[0][0]).toBe('/bar');
42+
expect(fs.writeFileSync.mock.calls[0][1]).toBe(afterPatch);
43+
});
44+
45+
test('isPatched() returns false if the file has not been patched', () => {
46+
const beforePatch = `\
47+
#import <UIKit/UIKit.h>
48+
#import <React/RCTRootView.h>
49+
50+
@implementation AppDelegate
51+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
52+
{
53+
return NO;
54+
}
55+
`;
56+
57+
fs.readFileSync = jest.fn().mockReturnValueOnce(beforePatch);
58+
const patcher = new ObjcPatcher('/foo', 'foo');
59+
expect(patcher.isPatched()).toBe(false);
60+
});
61+
62+
test('isPatched() returns true if the file has been patched', () => {
63+
const afterPatch = `\
64+
/* Patched by ObjcPatcher: foo */
65+
#import <UIKit/UIKit.h>
66+
#import <React/RCTRootView.h>
67+
68+
@implementation AppDelegate
69+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
70+
{
71+
return NO;
72+
}
73+
`;
74+
75+
fs.readFileSync = jest.fn().mockReturnValueOnce(afterPatch);
76+
const patcher = new ObjcPatcher('/foo', 'foo');
77+
expect(patcher.isPatched()).toBe(true);
78+
});
79+
80+
test('addFunction() will add code block before last @end', () => {
81+
const beforePatch = `\
82+
#import <UIKit/UIKit.h>
83+
#import <React/RCTRootView.h>
84+
85+
@implementation AppDelegate
86+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
87+
{
88+
self.window.rootViewController = rootViewController;
89+
return NO;
90+
}
91+
@end
92+
`;
93+
94+
const afterPatch = `\
95+
/* Patched by ObjcPatcher: foo */
96+
#import <UIKit/UIKit.h>
97+
#import <React/RCTRootView.h>
98+
99+
@implementation AppDelegate
100+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
101+
{
102+
self.window.rootViewController = rootViewController;
103+
return NO;
104+
}
105+
106+
- (void)foo
107+
{
108+
}
109+
@end
110+
`;
111+
112+
fs.readFileSync = jest.fn().mockReturnValueOnce(beforePatch);
113+
114+
fs.writeFileSync = jest.fn();
115+
116+
const patcher = new ObjcPatcher('/foo', 'foo');
117+
const addCode = `
118+
- (void)foo
119+
{
120+
}`;
121+
patcher.addFunction(addCode).write('/bar');
122+
expect(fs.writeFileSync.mock.calls[0][1]).toBe(afterPatch);
123+
});
124+
125+
test('replace() is simply passthrough String.replace', () => {
126+
const beforePatch = `\
127+
#import <UIKit/UIKit.h>
128+
#import <React/RCTRootView.h>
129+
130+
@implementation AppDelegate
131+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
132+
{
133+
self.window.rootViewController = rootViewController;
134+
return NO;
135+
}
136+
`;
137+
138+
const afterPatch = `\
139+
/* Patched by ObjcPatcher: foo */
140+
#import <UIKit/UIKit.h>
141+
#import <React/RCTRootView.h>
142+
143+
@implementation AppDelegate
144+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
145+
{
146+
self.window.rootViewController = rootViewController;
147+
dispatch_async(dispatch_get_main_queue(), ^{
148+
NSLog(@"AsyncTask");
149+
});
150+
return NO;
151+
}
152+
`;
153+
154+
fs.readFileSync = jest.fn().mockReturnValueOnce(beforePatch);
155+
156+
fs.writeFileSync = jest.fn();
157+
158+
const patcher = new ObjcPatcher('/foo', 'foo');
159+
const patchCode = `\
160+
dispatch_async(dispatch_get_main_queue(), ^{
161+
NSLog(@"AsyncTask");
162+
});`;
163+
patcher
164+
.replace(
165+
/(^\s*self.window.rootViewController = rootViewController;\s*$)/m,
166+
`$1\n${patchCode}`,
167+
)
168+
.write('/bar');
169+
expect(fs.writeFileSync.mock.calls[0][1]).toBe(afterPatch);
170+
});
171+
});

0 commit comments

Comments
 (0)