From a1ec7520199d10e0f851b0eddb74489edeaef9e4 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Tue, 7 Apr 2015 21:50:24 -0700 Subject: [PATCH] [ReactNative] Do flow check when running packager --- React/Base/RCTBridge.m | 9 +-- React/Base/RCTJavaScriptLoader.m | 21 +++-- React/Base/RCTRedBox.m | 3 +- packager/getFlowTypeCheckMiddleware.js | 86 +++++++++++++++++++++ packager/packager.js | 6 ++ packager/react-packager/src/Server/index.js | 6 ++ 6 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 packager/getFlowTypeCheckMiddleware.js diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index aa2ef7ee9..fafe34bb2 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -606,12 +606,11 @@ static id _latestJSExecutor; } else { [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification object:self]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reload) - name:RCTReloadNotification - object:nil]; - ; } + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reload) + name:RCTReloadNotification + object:nil]; }]; } } diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index 1d61946b9..3d2974e98 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -104,15 +104,20 @@ if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) { NSDictionary *userInfo; NSDictionary *errorDetails = RCTJSONParse(rawText, nil); - if ([errorDetails isKindOfClass:[NSDictionary class]]) { + if ([errorDetails isKindOfClass:[NSDictionary class]] && + [errorDetails[@"errors"] isKindOfClass:[NSArray class]]) { + NSMutableArray *fakeStack = [[NSMutableArray alloc] init]; + for (NSDictionary *err in errorDetails[@"errors"]) { + [fakeStack addObject: @{ + @"methodName": err[@"description"] ?: @"", + @"file": err[@"filename"] ?: @"", + @"lineNumber": err[@"lineNumber"] ?: @0 + }]; + } userInfo = @{ - NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", - @"stack": @[@{ - @"methodName": errorDetails[@"description"] ?: @"", - @"file": errorDetails[@"filename"] ?: @"", - @"lineNumber": errorDetails[@"lineNumber"] ?: @0 - }] - }; + NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided", + @"stack": fakeStack, + }; } else { userInfo = @{NSLocalizedDescriptionKey: rawText}; } diff --git a/React/Base/RCTRedBox.m b/React/Base/RCTRedBox.m index de3c7dda8..f5e8fbbb4 100644 --- a/React/Base/RCTRedBox.m +++ b/React/Base/RCTRedBox.m @@ -172,6 +172,7 @@ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; cell.textLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9]; cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14]; + cell.textLabel.numberOfLines = 2; cell.detailTextLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7]; cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11]; cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; @@ -196,7 +197,7 @@ CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; return ceil(boundingRect.size.height) + 40; } else { - return 44; + return 50; } } diff --git a/packager/getFlowTypeCheckMiddleware.js b/packager/getFlowTypeCheckMiddleware.js new file mode 100644 index 000000000..e0f98bde2 --- /dev/null +++ b/packager/getFlowTypeCheckMiddleware.js @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +var exec = require('child_process').exec; + +function getFlowTypeCheckMiddleware(options) { + return function(req, res, next) { + if (options.skipflow) { + return next(); + } + if (options.flowroot || options.projectRoots.length === 1) { + var flowroot = options.flowroot || options.projectRoots[0]; + } else { + console.warn('flow: No suitable root'); + return next(); + } + exec('command -v flow >/dev/null 2>&1', function(error, stdout) { + if (error) { + console.warn('flow: Skipping because not installed. Install with ' + + '`brew install flow`.'); + return next(); + } else { + return doFlowTypecheck(res, flowroot, next); + } + }); + }; +} + +function doFlowTypecheck(res, flowroot, next) { + var flowCmd = 'cd "' + flowroot + '" && flow --json --timeout 20'; + var start = Date.now(); + console.log('flow: Running static typechecks.'); + exec(flowCmd, function(flowError, stdout) { + if (!flowError) { + console.log('flow: Typechecks passed (' + (Date.now() - start) + 'ms).'); + return next(); + } else { + try { + var flowResponse = JSON.parse(stdout); + var errors = []; + var errorNum = 1; + flowResponse.errors.forEach(function(err) { + // flow errors are paired across callsites, so we indent and prefix to + // group them + var indent = ''; + err.message.forEach(function(msg) { + errors.push({ + description: indent + 'E' + errorNum + ': ' + msg.descr, + filename: msg.path, + lineNumber: msg.line, + column: msg.start, + }); + indent = ' '; + }); + errorNum++; + }); + var message = 'Flow found type errors. If you think these are wrong, ' + + 'make sure flow is up to date, or disable with --skipflow.'; + } catch (e) { + var message = + 'Flow failed to provide parseable output:\n\n`' + stdout + '`'; + console.error(message, '\nException: `', e, '`\n\n'); + } + var error = { + status: 500, + message: message, + type: 'FlowError', + errors: errors, + }; + console.error('flow: Error running command `' + flowCmd + '`:\n', error); + res.writeHead(error.status, { + 'Content-Type': 'application/json; charset=UTF-8', + }); + res.end(JSON.stringify(error)); + } + }); +} + +module.exports = getFlowTypeCheckMiddleware; diff --git a/packager/packager.js b/packager/packager.js index 55004b7cf..a67b723d3 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -13,6 +13,8 @@ var path = require('path'); var exec = require('child_process').exec; var http = require('http'); +var getFlowTypeCheckMiddleware = require('./getFlowTypeCheckMiddleware'); + if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) { console.log( '\n' + @@ -40,6 +42,9 @@ var options = parseCommandLine([{ }, { command: 'assetRoots', description: 'specify the root directories of app assets' +}, { + command: 'skipflow', + description: 'Disable flow checks' }]); if (options.projectRoots) { @@ -203,6 +208,7 @@ function runServer( .use(openStackFrameInEditor) .use(getDevToolsLauncher(options)) .use(statusPageMiddleware) + .use(getFlowTypeCheckMiddleware(options)) .use(getAppMiddleware(options)); options.projectRoots.forEach(function(root) { diff --git a/packager/react-packager/src/Server/index.js b/packager/react-packager/src/Server/index.js index 655075815..617359bf8 100644 --- a/packager/react-packager/src/Server/index.js +++ b/packager/react-packager/src/Server/index.js @@ -320,6 +320,12 @@ function handleError(res, error) { }); if (error.type === 'TransformError' || error.type === 'NotFoundError') { + error.errors = [{ + description: error.description, + filename: error.filename, + lineNumber: error.lineNumber, + }]; + console.error(error); res.end(JSON.stringify(error)); } else { console.error(error.stack || error);