prepack/scripts/debug-fb-www.js
Dominic Gannaway 4dda80e5be Add no-use-before-define to debug-fb-www script
Summary:
Release notes: none

This adds the `no-use-before-define` rule to `debug-fb-www` script. `variables` and `functions` are set to false, which allows them to be skipped if defined in a parent scope or if the function is in the same scope (as it gets hoisted), see: https://github.com/eslint/eslint/pull/7948/files. With our current internal bundle and latest master, this brings about a new error that wasn't previously emitted: `'_$Fg' was used before it was defined. (5525:15)`.  Fixes https://github.com/facebook/prepack/issues/1982
Closes https://github.com/facebook/prepack/pull/1984

Differential Revision: D8055274

Pulled By: trueadm

fbshipit-source-id: 93a672dd22bce8cd798ebbb7ed90536a847f9ecb
2018-05-18 02:16:46 -07:00

261 lines
7.8 KiB
JavaScript

/**
* Copyright (c) 2017-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.
*/
/* @flow */
// NOTE:
// put the input fb-www file in ${root}/fb-www/input.js
// the compiled file will be saved to ${root}/fb-www/output.js
let prepackSources = require("../lib/prepack-node.js").prepackSources;
let path = require("path");
let { readFile, writeFile, existsSync } = require("fs");
let { promisify } = require("util");
let readFileAsync = promisify(readFile);
let writeFileAsync = promisify(writeFile);
let chalk = require("chalk");
let { Linter } = require("eslint");
let errorsCaptured = [];
function printError(error) {
let msg = `${error.errorCode}: ${error.message}`;
if (error.location) {
let loc_start = error.location.start;
let loc_end = error.location.end;
msg += ` at ${loc_start.line}:${loc_start.column} to ${loc_end.line}:${loc_end.column}`;
}
switch (error.severity) {
case "Information":
console.log(`Info: ${msg}`);
return "Recover";
case "Warning":
console.warn(`Warn: ${msg}`);
return "Recover";
case "RecoverableError":
console.error(`Error: ${msg}`);
return "Fail";
case "FatalError":
console.error(`Fatal Error: ${msg}`);
return "Fail";
default:
console.log(msg);
}
}
let prepackOptions = {
errorHandler: diag => {
if (diag.severity === "Information") {
console.log(diag.message);
return "Recover";
}
errorsCaptured.push(diag);
if (diag.severity !== "Warning") {
if (diag.errorCode === "PP0025") {
// recover from `unable to evaluate "key" and "ref" on a ReactElement due to an abstract config passed to createElement`
return "Recover";
}
return "Fail";
}
return "Recover";
},
compatibility: "fb-www",
internalDebug: true,
serialize: true,
uniqueSuffix: "",
maxStackDepth: 100,
reactEnabled: true,
reactOutput: "jsx",
reactVerbose: true,
inlineExpressions: true,
invariantLevel: 0,
abstractValueImpliesMax: 1000,
};
let inputPath = path.resolve("fb-www/input.js");
let outputPath = path.resolve("fb-www/output.js");
let componentsListPath = path.resolve("fb-www/components.txt");
let components = new Map();
let startTime = Date.now();
let uniqueEvaluatedComponents = 0;
function compileSource(source) {
let serialized;
try {
serialized = prepackSources([{ filePath: inputPath, fileContents: source, sourceMapContents: "" }], prepackOptions);
} catch (e) {
console.log(`\n${chalk.inverse(`=== Diagnostics Log ===`)}\n`);
errorsCaptured.forEach(error => printError(error));
throw e;
}
return {
stats: serialized.reactStatistics,
code: serialized.code,
};
}
async function readComponentsList() {
if (existsSync(componentsListPath)) {
let componentsList = await readFileAsync(componentsListPath, "utf8");
let componentNames = componentsList.split("\n");
for (let componentName of componentNames) {
components.set(componentName, "missing");
}
}
}
function lintCompiledSource(source) {
let linter = new Linter();
let errors = linter.verify(source, {
env: {
commonjs: true,
browser: true,
},
rules: {
"no-undef": "error",
"no-use-before-define": ["error", { variables: false, functions: false }],
},
parserOptions: {
ecmaVersion: 6,
ecmaFeatures: {
jsx: true,
},
},
globals: {
// FB
React: true,
Env: true,
Bootloader: true,
JSResource: true,
babelHelpers: true,
regeneratorRuntime: true,
asset: true,
cx: true,
cssVar: true,
csx: true,
errorDesc: true,
errorHelpCenterID: true,
errorSummary: true,
gkx: true,
glyph: true,
ifRequired: true,
ix: true,
fbglyph: true,
fbt: true,
requireWeak: true,
xuiglyph: true,
// ES 6
Promise: true,
Map: true,
Set: true,
Proxy: true,
Symbol: true,
WeakMap: true,
// Vendor specific
MSApp: true,
ActiveXObject: true,
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,
// CommonJS / Node
process: true,
},
});
if (errors.length > 0) {
console.log(`\n${chalk.inverse(`=== Validation Failed ===`)}\n`);
for (let error of errors) {
console.log(`${chalk.red(error.message)} ${chalk.gray(`(${error.line}:${error.column})`)}`);
}
process.exit(1);
}
}
async function compileFile() {
let source = await readFileAsync(inputPath, "utf8");
let { stats, code } = await compileSource(source);
await writeFileAsync(outputPath, code);
lintCompiledSource(code);
// $FlowFixMe: no idea what this is about
return stats;
}
function printReactEvaluationGraph(evaluatedRootNode, depth) {
if (Array.isArray(evaluatedRootNode)) {
for (let child of evaluatedRootNode) {
printReactEvaluationGraph(child, depth);
}
} else {
let status = evaluatedRootNode.status.toLowerCase();
let message = evaluatedRootNode.message !== "" ? `: ${evaluatedRootNode.message}` : "";
let name = evaluatedRootNode.name;
let line;
if (status === "inlined") {
line = `${chalk.gray(`-`)} ${chalk.green(name)} ${chalk.gray(`(${status + message})`)}`;
} else if (status === "unknown_type" || status === "bail-out" || status === "fatal") {
line = `${chalk.gray(`-`)} ${chalk.red(name)} ${chalk.gray(`(${status + message})`)}`;
} else {
line = `${chalk.gray(`-`)} ${chalk.yellow(name)} ${chalk.gray(`(${status + message})`)}`;
}
if (components.has(name)) {
let currentStatus = components.get(name);
if (currentStatus === "missing") {
uniqueEvaluatedComponents++;
currentStatus = [currentStatus];
components.set(name, currentStatus);
} else if (Array.isArray(currentStatus)) {
currentStatus.push(status);
}
}
console.log(line.padStart(line.length + depth));
printReactEvaluationGraph(evaluatedRootNode.children, depth + 2);
}
}
readComponentsList()
.then(compileFile)
.then(result => {
console.log(`\n${chalk.inverse(`=== Compilation Complete ===`)}\n`);
console.log(chalk.bold(`Evaluated Tree:`));
printReactEvaluationGraph(result.evaluatedRootNodes, 0);
if (components.size > 0) {
console.log(`\n${chalk.inverse(`=== Status ===`)}\n`);
for (let [componentName, status] of components) {
if (status === "missing") {
console.log(`${chalk.red(``)} ${componentName}`);
} else {
console.log(`${chalk.green(``)} ${componentName}`);
}
}
}
console.log(`\n${chalk.inverse(`=== Summary ===`)}\n`);
if (components.size > 0) {
console.log(`${chalk.gray(`Optimized Components`)}: ${uniqueEvaluatedComponents}/${components.size}`);
}
console.log(`${chalk.gray(`Optimized Nodes`)}: ${result.componentsEvaluated}`);
console.log(`${chalk.gray(`Inlined Nodes`)}: ${result.inlinedComponents}`);
console.log(`${chalk.gray(`Optimized Trees`)}: ${result.optimizedTrees}`);
console.log(`${chalk.gray(`Optimized Nested Closures`)}: ${result.optimizedNestedClosures}`);
let timeTaken = Math.floor((Date.now() - startTime) / 1000);
console.log(`${chalk.gray(`Compile time`)}: ${timeTaken}s\n`);
// warning about ref and keys
console.log(`Warning: the build assumes that ref and key aren't being spread.`);
})
.catch(e => {
console.log(`\n${chalk.inverse(`=== Compilation Failed ===`)}\n`);
if (e.__isReconcilerFatalError) {
console.error(e.message + "\n");
printReactEvaluationGraph(e.evaluatedNode, 0);
} else {
console.error(e.nativeStack || e.stack);
}
process.exit(1);
});