You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
761 lines
33 KiB
761 lines
33 KiB
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.Formatter = void 0;
|
|
const code_frame_1 = require("@babel/code-frame");
|
|
const chalk_1 = __importDefault(require("chalk"));
|
|
const fs = __importStar(require("fs"));
|
|
const os = __importStar(require("os"));
|
|
const path = __importStar(require("path"));
|
|
const Matchers_1 = require("./Matchers");
|
|
const Parser_1 = require("./Parser");
|
|
const switchRegex_1 = require("./switchRegex");
|
|
const symbols_1 = require("./utils/symbols");
|
|
var Status;
|
|
(function (Status) {
|
|
Status["Pass"] = "pass";
|
|
Status["Fail"] = "fail";
|
|
Status["Pending"] = "pending";
|
|
Status["Error"] = "error";
|
|
Status["Completion"] = "completion";
|
|
Status["Measure"] = "measure";
|
|
})(Status || (Status = {}));
|
|
function highlightLastPathComponent(filePath) {
|
|
return chalk_1.default.dim(path.dirname(filePath) + '/') + path.basename(filePath);
|
|
}
|
|
function format(command, argumentText = '', success = true) {
|
|
const symbol = statusSymbol(success ? Status.Completion : Status.Fail);
|
|
return [symbol, chalk_1.default.bold(command), argumentText].join(' ').trim();
|
|
}
|
|
function formatTest(testCase, status) {
|
|
return [statusSymbol(status), testCase].join(' ').trim();
|
|
}
|
|
function heading(prefix, text, description) {
|
|
return [prefix, chalk_1.default.white(text), description].join(' ').trim();
|
|
}
|
|
function statusSymbol(status) {
|
|
switch (status) {
|
|
case Status.Pass:
|
|
return chalk_1.default.green(symbols_1.PASS);
|
|
case Status.Fail:
|
|
return chalk_1.default.red(symbols_1.FAIL);
|
|
case Status.Pending:
|
|
return chalk_1.default.cyan(symbols_1.PENDING);
|
|
case Status.Error:
|
|
return chalk_1.default.red(symbols_1.ERROR);
|
|
case Status.Completion:
|
|
return chalk_1.default.white(symbols_1.COMPLETION);
|
|
case Status.Measure:
|
|
return chalk_1.default.magenta(symbols_1.MEASURE);
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
function coloredTime(time) {
|
|
const flt = parseFloat(time);
|
|
if (flt >= 0 && flt <= 0.025) {
|
|
return time;
|
|
}
|
|
else if (flt >= 0.026 && flt <= 0.1) {
|
|
return chalk_1.default.yellow(time);
|
|
}
|
|
return chalk_1.default.red(time);
|
|
}
|
|
function capitalize(string) {
|
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
}
|
|
function relativePath(projectRoot, filePath) {
|
|
return slash(path.relative(projectRoot, filePath));
|
|
}
|
|
function formatBreadCrumb(fileName, target, project) {
|
|
// TODO: Simplify
|
|
return [project === target ? project : [project, target].filter(Boolean).join(`/`), fileName]
|
|
.filter(Boolean)
|
|
.join(` ${symbols_1.BREADCRUMB} `);
|
|
}
|
|
function getAppRoot(filePath) {
|
|
let appRoot = filePath;
|
|
const isBuildFolder = (path) => /((Debug|Release)-[^/\s\d]*$)|(.app$)/.test(path);
|
|
while (!isBuildFolder(appRoot) && appRoot.length > 1) {
|
|
appRoot = path.dirname(appRoot);
|
|
}
|
|
return isBuildFolder(appRoot) ? appRoot : '';
|
|
}
|
|
class Formatter {
|
|
constructor(props) {
|
|
this.props = props;
|
|
this.errors = [];
|
|
this.warnings = [];
|
|
}
|
|
get parser() {
|
|
if (this._parser) {
|
|
return this._parser;
|
|
}
|
|
this._parser = new Parser_1.Parser(this);
|
|
return this._parser;
|
|
}
|
|
pipe(data) {
|
|
const lines = [];
|
|
data.split(os.EOL).forEach(line => {
|
|
const results = this.parser.parse(line);
|
|
if (typeof results === 'string' && results.trim()) {
|
|
lines.push(results);
|
|
}
|
|
});
|
|
return lines;
|
|
}
|
|
dimConfiguration(configuration) {
|
|
return chalk_1.default.dim(`(${configuration})`);
|
|
}
|
|
getTitleForConfigurationType(type) {
|
|
switch (type) {
|
|
case 'Clean':
|
|
return 'Cleaning';
|
|
case 'Aggregate':
|
|
return 'Aggregate';
|
|
case 'Analyze':
|
|
return 'Analyzing';
|
|
case 'Build':
|
|
return 'Building';
|
|
default:
|
|
return 'Unknown';
|
|
}
|
|
}
|
|
formatTarget(props) {
|
|
return format(this.getTitleForConfigurationType(props.type), this.dimConfiguration(formatBreadCrumb(props.configuration, props.target, props.project)));
|
|
}
|
|
formatCopy({ from, to }) {
|
|
const relativeFile = relativePath(this.props.projectRoot, from);
|
|
const appFileRoot = getAppRoot(to);
|
|
const relativeAppFile = relativePath(appFileRoot, to);
|
|
return format('Copying ', [relativeFile, relativeAppFile].join(' ➜ '));
|
|
}
|
|
getFileOperationTitle(type) {
|
|
switch (type) {
|
|
case 'Analyze':
|
|
return 'Analyzing';
|
|
case 'GenerateDSYMFile':
|
|
return `Generating debug`;
|
|
case 'Ld':
|
|
return 'Linking ';
|
|
case 'Libtool':
|
|
return 'Packaging';
|
|
case 'ProcessPCH':
|
|
return 'Precompiling';
|
|
case 'ProcessInfoPlistFile':
|
|
return 'Preparing';
|
|
case 'CodeSign':
|
|
return 'Signing ';
|
|
case 'Touch':
|
|
return 'Creating ';
|
|
case 'CompileC':
|
|
case 'CompileSwift':
|
|
case 'CompileXIB':
|
|
case 'CompileStoryboard':
|
|
return 'Compiling';
|
|
default:
|
|
// Unknown file operation
|
|
return '';
|
|
}
|
|
}
|
|
formatFileOperation(props) {
|
|
const title = this.getFileOperationTitle(props.type);
|
|
switch (props.type) {
|
|
case 'Analyze':
|
|
return format(title, formatBreadCrumb(props.fileName, props.target, props.project));
|
|
case 'GenerateDSYMFile':
|
|
return format(title, formatBreadCrumb(`'${props.fileName}'`, props.target, props.project));
|
|
case 'Ld':
|
|
return format(title, formatBreadCrumb(props.fileName, props.target, props.project));
|
|
case 'Libtool':
|
|
return format(title, formatBreadCrumb(props.fileName, props.target, props.project));
|
|
case 'ProcessPCH':
|
|
return format(title, formatBreadCrumb(props.fileName, props.target, props.project));
|
|
case 'ProcessInfoPlistFile':
|
|
return format(title, formatBreadCrumb(props.fileName, props.target, props.project));
|
|
case 'CodeSign':
|
|
return format(title, formatBreadCrumb(props.fileName, props.target, props.project));
|
|
case 'Touch':
|
|
return format(title, props.fileName);
|
|
case 'CompileC':
|
|
case 'CompileSwift':
|
|
case 'CompileXIB':
|
|
case 'CompileStoryboard':
|
|
return format(title, formatBreadCrumb(props.fileName, props.target, props.project));
|
|
default:
|
|
// Unknown file operation
|
|
return '';
|
|
}
|
|
}
|
|
formatPhaseSuccess(phaseName, duration) {
|
|
return format(capitalize(phaseName.toLowerCase()), `Succeeded${duration ? ` (${duration})` : ''}`, true);
|
|
}
|
|
formatPhaseScriptExecution(scriptName, target, project) {
|
|
// TODO: Track (scriptName === '[CP] Copy XCFrameworks')
|
|
return format('Executing', formatBreadCrumb(`'${scriptName}'`, target, project));
|
|
}
|
|
formatPreprocess(file) {
|
|
return format('Preprocessing', file);
|
|
}
|
|
formatShellCommand(command, args) {
|
|
return '';
|
|
}
|
|
formatCompileCommand(compilerCommand, filePath) {
|
|
return '';
|
|
}
|
|
formatProcessPchCommand(filePath) {
|
|
return '';
|
|
}
|
|
formatWriteFile(file) {
|
|
return '';
|
|
}
|
|
formatOther(text) {
|
|
return '';
|
|
}
|
|
formatSingleLineCompileIssue(type, filePathAndLocation, fileName, reason, target, project) {
|
|
// Allow disabling this warning.
|
|
const { filePath, lineNumber, columnNumber } = splitPathInfo(filePathAndLocation);
|
|
if (type === 'warning') {
|
|
if (!this.shouldShowCompileWarning(filePath, lineNumber, columnNumber)) {
|
|
return '';
|
|
}
|
|
}
|
|
// Prevent `/foo/bar:1:1` instead using `/foo/bar` since it's a bit more focused.
|
|
const sanitizedFilePath = lineNumber === '1' && columnNumber === '1' ? filePath : filePathAndLocation;
|
|
// Get the `Project/Target` prefix.
|
|
const packageName = [project, target].join('/');
|
|
// Choose a color.
|
|
const color = type === 'warning' ? chalk_1.default.yellow : chalk_1.default.red;
|
|
const platform = color.bold(`${packageName}:`);
|
|
// Choose a symbol.
|
|
const symbol = type === 'warning' ? symbols_1.WARNING : symbols_1.ERROR;
|
|
// Get a more concise file path when the issue is inside the build folder.
|
|
const appFileRoot = getAppRoot(sanitizedFilePath);
|
|
const relativeAppFile = appFileRoot
|
|
? chalk_1.default.gray('[app]/') + relativePath(appFileRoot, sanitizedFilePath)
|
|
: sanitizedFilePath;
|
|
// Create the message.
|
|
const results = chalk_1.default `${symbol} ${platform} ${reason.trim()}\n {gray └─${relativeAppFile}}`;
|
|
// Ensure we track the message
|
|
if (type === 'warning') {
|
|
this.warnings.push(results);
|
|
}
|
|
else {
|
|
this.errors.push(results);
|
|
}
|
|
return results;
|
|
}
|
|
// These are like comments but for Xcode logs.
|
|
formatRemark(msg) {
|
|
return '';
|
|
}
|
|
formatEmitSwiftModule(type, arch, target, project) {
|
|
return '';
|
|
}
|
|
formatCompileSwiftSources(type, arch, pkg, target, project) {
|
|
return '';
|
|
}
|
|
formatCleanRemove(msg) {
|
|
return '';
|
|
}
|
|
formatWriteAuxiliaryFiles(text) {
|
|
return '';
|
|
}
|
|
formatTiffutil(file) {
|
|
return format('Validating', file);
|
|
}
|
|
formatCheckDependencies(text) {
|
|
return format('Check Dependencies');
|
|
}
|
|
formatWillNotBeCodeSigned(message) {
|
|
const results = `${chalk_1.default.yellow(symbols_1.WARNING + ' ' + message)}`;
|
|
this.warnings.push(results);
|
|
return results;
|
|
}
|
|
// COMPILER / LINKER ERRORS AND WARNINGS
|
|
/**
|
|
*
|
|
* @param fileName 'SampleTest.m',
|
|
* @param filePathAndLocation '/Users/foo/bar.m:12:59',
|
|
* @param reason 'expected identifier',
|
|
* @param line ' [[thread should] equal:thread.];',
|
|
* @param cursor ' ^'
|
|
*/
|
|
formatCompileError(fileName, filePathAndLocation, reason, line, cursor) {
|
|
const { filePath, lineNumber, columnNumber } = splitPathInfo(filePathAndLocation);
|
|
const results = formatWarningOrError({
|
|
isError: true,
|
|
filePath,
|
|
reason,
|
|
cursor,
|
|
lineText: line,
|
|
lineNumber,
|
|
columnNumber,
|
|
projectRoot: this.props.projectRoot,
|
|
maxWarningLineLength: this.props.maxWarningLineLength,
|
|
});
|
|
this.errors.push(results);
|
|
return results;
|
|
}
|
|
formatError(message) {
|
|
const results = switchRegex_1.switchRegex(message, [
|
|
[
|
|
Matchers_1.Matchers.Errors.UNSUPPORTED_ENTITLEMENT_MATCHER,
|
|
([, $1, $2, $3, $4, $5]) => {
|
|
return this.formatUnsupportedEntitlementError($1, $2, $3, $4, $5);
|
|
},
|
|
],
|
|
[null, () => this.formatGenericError(message)],
|
|
]);
|
|
this.errors.push(results);
|
|
return results;
|
|
}
|
|
/**
|
|
* In: `error: Provisioning profile "iOS Team Provisioning Profile: *" doesn't support the Push Notifications capability. (in target 'yolo90' from project 'yolo90')`
|
|
* Out: `❌ yolo90/yolo90: Provisioning Profile "iOS Team Provisioning Profile: *" does not support the Push Notifications capability.`
|
|
*
|
|
* In: `error: Provisioning profile "iOS Team Provisioning Profile: *" doesn't include the aps-environment entitlement. (in target 'yolo90' from project 'yolo90')`
|
|
* Out: `❌ yolo90/yolo90: Entitlements file defines the value "aps-environment" which is not registered for profile "iOS Team Provisioning Profile: *".`
|
|
*
|
|
* @param profileName `"iOS Team Provisioning Profile: *"`
|
|
* @param entitlementName `Push Notifications` | `aps-environment`
|
|
* @param entitlementType `capability` | `entitlement`
|
|
* @param target boost-for-react-native
|
|
* @param project Pods
|
|
*/
|
|
formatUnsupportedEntitlementError(profileName, entitlementName, entitlementType, target, project) {
|
|
const packageName = [project, target].join('/');
|
|
const platform = chalk_1.default.red.bold(`${packageName}:`);
|
|
if (entitlementType === 'capability') {
|
|
return chalk_1.default `${symbols_1.ERROR} ${platform} Provisioning Profile ${profileName} does not support the {red ${entitlementName}} capability.`;
|
|
}
|
|
return chalk_1.default `${symbols_1.ERROR} ${platform} Entitlements file defines the value {red "${entitlementName}"} which is not registered for profile ${profileName}.`;
|
|
}
|
|
formatFileMissingError(reason, filePath) {
|
|
const results = `\n${chalk_1.default.red(symbols_1.ERROR + ' ' + reason)} ${filePath}\n\n`;
|
|
this.errors.push(results);
|
|
return results;
|
|
}
|
|
formatLdWarning(reason) {
|
|
const results = switchRegex_1.switchRegex(reason, [
|
|
[
|
|
Matchers_1.Matchers.Warnings.LINKER_METHOD_OVERRIDE,
|
|
([, $1, $2, $3, $4, $5]) => {
|
|
return this.formatLdMethodOverride($1, [
|
|
{ filePath: $2, name: $3 },
|
|
{ filePath: $4, name: $5 },
|
|
]);
|
|
},
|
|
],
|
|
[null, () => `${chalk_1.default.yellow(symbols_1.WARNING + ' ' + reason)}`],
|
|
]);
|
|
this.warnings.push(results);
|
|
return results;
|
|
}
|
|
formatUndefinedSymbols(message, symbol, reference) {
|
|
const symbols = chalk_1.default.gray(`┌─ Symbol: ${symbol}\n└─ Referenced from: ${reference}`);
|
|
const results = `${chalk_1.default.red(symbols_1.ERROR + ' ' + message)}\n${symbols}\n`;
|
|
this.errors.push(results);
|
|
return results;
|
|
}
|
|
formatLdMethodOverride(methodName, collisions) {
|
|
const formattedMessage = chalk_1.default.yellow(symbols_1.WARNING + ` ld: duplicate method '${chalk_1.default.bold(methodName)}' in`);
|
|
const types = ['category', 'class'];
|
|
const symbols = chalk_1.default.gray(collisions
|
|
.map(({ filePath, name }, i) => {
|
|
const appFileRoot = getAppRoot(filePath);
|
|
const relativeAppFile = relativePath(appFileRoot, filePath);
|
|
const branch = i === collisions.length - 1 ? '└─' : i === 0 ? '┌─' : '├─';
|
|
return `${branch}${`[${types[i]}]`}: ${name} ${chalk_1.default.dim(relativeAppFile)}`;
|
|
})
|
|
.join('\n'));
|
|
return `${formattedMessage}\n${symbols}\n`;
|
|
}
|
|
formatDuplicateSymbols(message, filePaths, isWarning) {
|
|
const formattedMessage = isWarning
|
|
? chalk_1.default.yellow(symbols_1.WARNING + ' ' + message)
|
|
: chalk_1.default.red(symbols_1.ERROR + ' ' + message);
|
|
const symbols = chalk_1.default.gray(filePaths
|
|
.map((p, i) => {
|
|
const branch = i === filePaths.length - 1 ? '└─' : i === 0 ? '┌─' : '├─';
|
|
return `${branch} ${path.basename(p)}`;
|
|
})
|
|
.join('\n'));
|
|
const results = `${formattedMessage}\n${symbols}\n`;
|
|
if (isWarning) {
|
|
this.warnings.push(results);
|
|
}
|
|
else {
|
|
this.errors.push(results);
|
|
}
|
|
return results;
|
|
}
|
|
/**
|
|
* In: `The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.3.99. (in target 'boost-for-react-native' from project 'Pods')`
|
|
* Out: `⚠️ Pods/boost-for-react-native: iOS@8.0 version mismatch. Expected >= 9.0 < 14.3.99`
|
|
*
|
|
* @param os iOS
|
|
* @param deploymentTarget IPHONEOS_DEPLOYMENT_TARGET
|
|
* @param version 8.0
|
|
* @param minVersion 9.0
|
|
* @param maxVersion 14.3.99
|
|
* @param target boost-for-react-native
|
|
* @param project Pods
|
|
*/
|
|
formatVersionMismatchWarning(os, deploymentTarget, version, minVersion, maxVersion, target, project) {
|
|
const packageName = [project, target].join('/');
|
|
const platform = chalk_1.default.bold(`${packageName}:`);
|
|
const packageNameWithVersion = chalk_1.default.greenBright(os) + chalk_1.default.cyan `@` + chalk_1.default.magenta(version);
|
|
const expectedRange = `>= ${minVersion} <= ${maxVersion}`;
|
|
return `${symbols_1.WARNING} ${platform} ${packageNameWithVersion} deployment version mismatch, expected ${expectedRange}`;
|
|
}
|
|
/**
|
|
* In: `warning: [CP] Vendored binary '/Users/evanbacon/Library/Developer/Xcode/DerivedData/yolo67-hcjsxsdqyxnsgdednlbpylgeffja/Build/Intermediates.noindex/Pods.build/Debug-iphonesimulator/hermes-engine.build/DerivedSources/hermes.framework.dSYM/Contents/Resources/DWARF/hermes' contains architectures (armv7 armv7s arm64) none of which match the current build architectures (x86_64).`
|
|
* Out: `⚠️ Vendored binary '[app]/hermes-engine.build/DerivedSources/hermes.framework.dSYM/Contents/Resources/DWARF/hermes' does not support current build architecture (x86_64). Supported architectures: armv7, armv7s, arm64.`
|
|
*
|
|
* @param os iOS
|
|
* @param deploymentTarget IPHONEOS_DEPLOYMENT_TARGET
|
|
* @param version 8.0
|
|
* @param minVersion 9.0
|
|
* @param maxVersion 14.3.99
|
|
* @param target boost-for-react-native
|
|
* @param project Pods
|
|
*/
|
|
formatMissingArchitectureWarning(binaryPath, architectures, currentArchitectures) {
|
|
const appFileRoot = getAppRoot(binaryPath);
|
|
const relativeAppFile = appFileRoot
|
|
? chalk_1.default.gray('[app]/') + relativePath(appFileRoot, binaryPath)
|
|
: binaryPath;
|
|
const architectureString = currentArchitectures.length === 1 ? 'architecture' : 'architectures';
|
|
const supportedString = chalk_1.default.dim(`Supported architectures: ${architectures.join(', ')}.`);
|
|
return (chalk_1.default.yellow(`${symbols_1.WARNING} Vendored binary '${relativeAppFile}' does not support current build ${architectureString} (${chalk_1.default.bold(currentArchitectures.join(', '))}). `) + supportedString);
|
|
}
|
|
/**
|
|
* In: `Skipping duplicate build file in Compile Sources build phase: /Users/evanbacon/Documents/GitHub/expo/ios/Exponent/Kernel/ReactAppManager/EXReactAppManager.mm (in target 'Exponent' from project 'Exponent')`
|
|
* Out:
|
|
* `⚠️ Skipping duplicate file: Exponent/Kernel/ReactAppManager/EXReactAppManager.mm:
|
|
* Remove: Exponent » Exponent » Build Phases » Compile Sources » EXReactAppManager.mm`
|
|
*
|
|
* @param filePath
|
|
* @param buildPhase 'Compile Sources'
|
|
* @param target Exponent-watch-app
|
|
* @param project Exponent
|
|
*/
|
|
formatDuplicateFileCompilerWarning(filePath, buildPhase, target, project) {
|
|
const message = `${chalk_1.default.yellow `Skipping duplicate file:`} ${relativePath(this.props.projectRoot, filePath)}`;
|
|
const fileName = path.basename(filePath);
|
|
const crumbs = chalk_1.default.gray('Remove: ' +
|
|
['Xcode', `${project}/${target}`, 'Build Phases', buildPhase, fileName].join(` ${symbols_1.BREADCRUMB} `));
|
|
return `${symbols_1.WARNING} ${message}\n ${crumbs}\n`;
|
|
}
|
|
/**
|
|
* In: `The Copy Bundle Resources build phase contains this target's Info.plist file '/Users/evanbacon/Documents/GitHub/expo/ios/Exponent/Supporting/Info.plist'. (in target 'Exponent' from project 'Exponent')`
|
|
* Out:
|
|
* `⚠️ Target's Info.plist file is incorrectly linked: Exponent/Supporting/Info.plist:
|
|
* Remove: Exponent » Exponent » Build Phases » Copy Bundle Resources » Info.plist`
|
|
*
|
|
* @param filePath
|
|
* @param reservedFileDescription 'entitlements'
|
|
* @param target Exponent-watch-app
|
|
* @param project Exponent
|
|
*/
|
|
formatReservedFileInCopyBundleResourcesCompilerWarning(filePath, reservedFileDescription, target, project) {
|
|
const message = `${chalk_1.default.yellow `Target's ${chalk_1.default.bold(reservedFileDescription)} file is incorrectly linked:`} ${relativePath(this.props.projectRoot, filePath)}`;
|
|
const fileName = path.basename(filePath);
|
|
const crumbs = chalk_1.default.gray('Remove: ' +
|
|
['Xcode', `${project}/${target}`, 'Build Phases', 'Copy Bundle Resources', fileName].join(` ${symbols_1.BREADCRUMB} `));
|
|
return `${symbols_1.WARNING} ${message}\n ${crumbs}\n`;
|
|
}
|
|
formatMissingFileCompilerWarning(filePath) {
|
|
return `${symbols_1.WARNING} ${chalk_1.default.yellow `No such file or directory:`} ${filePath}`;
|
|
}
|
|
formatGenericError(message) {
|
|
return `\n${chalk_1.default.red(symbols_1.ERROR + ' ' + message)}\n\n`;
|
|
}
|
|
formatGenericWarning(message) {
|
|
return symbols_1.INDENT + chalk_1.default.yellow(message);
|
|
}
|
|
formatWarning(message) {
|
|
const results = switchRegex_1.switchRegex(message, [
|
|
[
|
|
Matchers_1.Matchers.Warnings.MISSING_ARCHITECTURE,
|
|
([, $1, $2, $3]) => {
|
|
return this.formatMissingArchitectureWarning($1, $2 === null || $2 === void 0 ? void 0 : $2.split(' ').map(value => value.trim()), $3 === null || $3 === void 0 ? void 0 : $3.split(' ').map(value => value.trim()));
|
|
},
|
|
],
|
|
[
|
|
Matchers_1.Matchers.Warnings.VERSION_MISMATCH,
|
|
([, $1, $2, $3, $4, $5, $6, $7]) => {
|
|
return this.formatVersionMismatchWarning($1, $2, $3, $4, $5, $6, $7);
|
|
},
|
|
],
|
|
[
|
|
Matchers_1.Matchers.Warnings.MISSING_FILE_COMPILER_WARNING_MATCHER,
|
|
([, $1]) => {
|
|
return this.formatMissingFileCompilerWarning($1);
|
|
},
|
|
],
|
|
[
|
|
Matchers_1.Matchers.Warnings.SKIPPING_DUPLICATE_FILE,
|
|
([, $1, $2, $3, $4]) => {
|
|
return this.formatDuplicateFileCompilerWarning($2, $1, $3, $4);
|
|
},
|
|
],
|
|
[
|
|
Matchers_1.Matchers.Warnings.TARGETS_FILE_INCLUDED,
|
|
([, $1, $2, $3, $4]) => {
|
|
return this.formatReservedFileInCopyBundleResourcesCompilerWarning($2, $1, $3, $4);
|
|
},
|
|
],
|
|
[null, () => this.formatGenericWarning(message)],
|
|
]);
|
|
this.warnings.push(results);
|
|
return results;
|
|
}
|
|
// TODO: see how we can unify formatError and formatCompileError,
|
|
// the same for warnings
|
|
formatCompileWarning(fileName, filePathAndLocation, reason, line, cursor) {
|
|
const { filePath, lineNumber, columnNumber } = splitPathInfo(filePathAndLocation);
|
|
if (this.shouldShowCompileWarning(filePath, lineNumber, columnNumber)) {
|
|
const results = formatWarningOrError({
|
|
isError: false,
|
|
filePath,
|
|
reason,
|
|
cursor,
|
|
lineText: line,
|
|
lineNumber,
|
|
columnNumber,
|
|
projectRoot: this.props.projectRoot,
|
|
maxWarningLineLength: this.props.maxWarningLineLength,
|
|
});
|
|
this.warnings.push(results);
|
|
return results;
|
|
}
|
|
return '';
|
|
}
|
|
shouldShowCompileWarning(filePath, lineNumber, columnNumber) {
|
|
return true;
|
|
}
|
|
formatPendingTest(suite, test) {
|
|
return symbols_1.INDENT + formatTest(`${test} [PENDING]`, Status.Pending);
|
|
}
|
|
formatPassingTest(suite, test, time) {
|
|
return symbols_1.INDENT + formatTest(`${test} (${coloredTime(time)} seconds)`, Status.Pass);
|
|
}
|
|
formatMeasuringTest(suite, test, time) {
|
|
return symbols_1.INDENT + formatTest(`${test} measured (${coloredTime(time)} seconds)`, Status.Measure);
|
|
}
|
|
formatFailingTest(suite, test, reason, filePath) {
|
|
return symbols_1.INDENT + formatTest(`${test}, ${reason}`, Status.Fail);
|
|
}
|
|
formatTestRunStarted(name) {
|
|
return heading('Test Suite', name, 'started');
|
|
}
|
|
formatTestSuiteStarted(name) {
|
|
return heading('', name, '');
|
|
}
|
|
formatTestRunFinished(name, time) {
|
|
return '';
|
|
}
|
|
// Will be printed by default. Override with '' if you don't want summary
|
|
formatTestSummary(executedMessage, failuresPerSuite) {
|
|
const failures = this.formatFailures(failuresPerSuite);
|
|
let finalMessage = '';
|
|
if (!failures) {
|
|
finalMessage = chalk_1.default.green(executedMessage);
|
|
}
|
|
else {
|
|
finalMessage = chalk_1.default.red(executedMessage);
|
|
}
|
|
const text = [failures, finalMessage].join('\n\n\n').trim();
|
|
return `\n\n${text}`;
|
|
}
|
|
formatFailures(failuresPerSuite) {
|
|
return Object.entries(failuresPerSuite)
|
|
.map(([suite, failures]) => {
|
|
const formattedFailures = failures.map(failure => this.formatFailure(failure)).join('\n\n');
|
|
return `\n${suite}\n${formattedFailures}`;
|
|
})
|
|
.join('\n');
|
|
}
|
|
formatFailure(f) {
|
|
const { filePath, lineNumber, columnNumber } = splitPathInfo(f.filePath);
|
|
return formatWarningOrError({
|
|
isError: true,
|
|
testName: f.testCase,
|
|
filePath,
|
|
reason: f.reason,
|
|
// cursor,
|
|
lineNumber,
|
|
columnNumber,
|
|
projectRoot: this.props.projectRoot,
|
|
maxWarningLineLength: this.props.maxWarningLineLength,
|
|
});
|
|
}
|
|
finish() { }
|
|
// Override if you want to catch something specific with your regex
|
|
prettyFormat(text) {
|
|
return this.parser.parse(text);
|
|
}
|
|
// If you want to print inline, override #optionalNewline with ''
|
|
optionalNewline() {
|
|
return '\n';
|
|
}
|
|
getBuildSummary() {
|
|
return `\n\u203A ${this.errors.length} error(s), and ${this.warnings.length} warning(s)\n`;
|
|
}
|
|
}
|
|
exports.Formatter = Formatter;
|
|
Formatter.format = format;
|
|
Formatter.formatBreadCrumb = formatBreadCrumb;
|
|
Formatter.getAppRoot = getAppRoot;
|
|
Formatter.highlightLastPathComponent = highlightLastPathComponent;
|
|
Formatter.relativePath = relativePath;
|
|
function formatPaths(config) {
|
|
const filePath = chalk_1.default.reset.cyan(config.filePath);
|
|
return (chalk_1.default.dim('(') +
|
|
filePath +
|
|
chalk_1.default.dim(`:${[config.line, config.col].filter(Boolean).join(':')})`));
|
|
}
|
|
/**
|
|
* Split a string like `/Users/foo/bar.m:420:68` into its components.
|
|
*
|
|
* @param filePath '/Users/foo/bar.m:420:68'
|
|
*/
|
|
function splitPathInfo(filePathAndLocation) {
|
|
const [path, line, column] = filePathAndLocation.split(':');
|
|
return {
|
|
filePath: path || filePathAndLocation,
|
|
lineNumber: line,
|
|
columnNumber: column,
|
|
};
|
|
}
|
|
function parseOptionalInt(text) {
|
|
if (!text)
|
|
return undefined;
|
|
try {
|
|
const result = parseInt(text, 10);
|
|
return isNaN(result) ? undefined : result;
|
|
}
|
|
catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
function formatWarningOrError({ projectRoot, filePath, reason, cursor, lineText, lineNumber, columnNumber, isError, maxWarningLineLength = 200, }) {
|
|
var _a;
|
|
const line = parseOptionalInt(lineNumber) || 0;
|
|
const column = parseOptionalInt(columnNumber);
|
|
const color = isError ? chalk_1.default.red : chalk_1.default.yellow;
|
|
const icon = color(isError ? symbols_1.ERROR : symbols_1.WARNING);
|
|
const displayFilePath = !filePath
|
|
? // If no file path, use null
|
|
null
|
|
: // If the file path is inside of the build folder (Hermes), then use absolute path.
|
|
getAppRoot(filePath)
|
|
? filePath
|
|
: // Otherwise, use relative path
|
|
slash(path.relative(projectRoot, filePath));
|
|
const formattedPath = formatPaths({
|
|
filePath: displayFilePath,
|
|
col: column,
|
|
line,
|
|
});
|
|
const pathWithPrefix = `${icon} ${formattedPath}`;
|
|
const formattedReason = color(grayOutMatch(reason, /(\[-.*?\])/).replace(/(\(.*?\)\s?)/, ''));
|
|
// Add special case for .jsbundle files that are parsed with Hermes.
|
|
const isHermes = filePath.endsWith('.jsbundle');
|
|
const isPreviewTooLong = isHermes || (lineText && lineText.length > maxWarningLineLength);
|
|
// When the preview is too long, we skip reading the file and attempting to apply
|
|
// code coloring, this is because it can get very slow.
|
|
if (isPreviewTooLong) {
|
|
let previewLine = '';
|
|
let cursorLine = formattedReason;
|
|
// Create a curtailed preview line like:
|
|
// `...transition:'fade'},k._updatePropsStack=function(){clearImmediate(k._updateImmediate),k._updateImmediate...`
|
|
// If there is no text preview or column number, we can't do anything.
|
|
if (lineText && column != null) {
|
|
const rangeWindow = Math.round(Math.max((_a = displayFilePath === null || displayFilePath === void 0 ? void 0 : displayFilePath.length) !== null && _a !== void 0 ? _a : 0, 80) / 2);
|
|
let minBounds = Math.max(0, column - rangeWindow);
|
|
const maxBounds = Math.min(minBounds + rangeWindow * 2, lineText.length);
|
|
previewLine = lineText.slice(minBounds, maxBounds);
|
|
// If we splice content off the start, then we should append `...`.
|
|
// This is unlikely to happen since we limit the activation size.
|
|
if (minBounds > 0) {
|
|
// Adjust the min bounds so the cursor is aligned after we add the "..."
|
|
minBounds -= 3;
|
|
previewLine = chalk_1.default.dim('...') + previewLine;
|
|
}
|
|
if (maxBounds < lineText.length) {
|
|
previewLine += chalk_1.default.dim('...');
|
|
}
|
|
// If the column property could be found, then use that to fix the cursor location which is often broken in regex.
|
|
cursorLine =
|
|
(column == null ? chalk_1.default.reset(cursor) : fill(column) + chalk_1.default.reset('^')).slice(minBounds) +
|
|
' ' +
|
|
formattedReason;
|
|
}
|
|
return ['', pathWithPrefix, '', previewLine, cursorLine, chalk_1.default.dim('(warning truncated)')].join('\n');
|
|
}
|
|
try {
|
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
const location = { start: { line, column } };
|
|
const framed = code_frame_1.codeFrameColumns(raw, location, {
|
|
// TODO: Support iOS languages: C++, Objc, swift, Ruby, Bash
|
|
// Maybe something like prism but for terminals?
|
|
highlightCode: false,
|
|
// Remove `(_Nonnull, _Nullable, or _Null_unspecified)` options
|
|
message: formattedReason,
|
|
});
|
|
return `\n${pathWithPrefix}\n\n${framed}\n`;
|
|
}
|
|
catch {
|
|
// If the column property could be found, then use that to fix the cursor location which is often broken in regex.
|
|
const customCursor = column == null ? chalk_1.default.reset(cursor) : fill(column) + chalk_1.default.reset('^');
|
|
const framed = `${lineText}\n${customCursor} ${formattedReason}`;
|
|
return `\n${pathWithPrefix}\n\n${framed}\n`;
|
|
}
|
|
}
|
|
function fill(width) {
|
|
return Array(width).join(' ');
|
|
}
|
|
// Dim values like `[-Wnullability-completeness]`
|
|
function grayOutMatch(text, reg) {
|
|
return replaceMatch(text, reg, chalk_1.default.gray.dim);
|
|
}
|
|
function replaceMatch(text, reg, callback) {
|
|
const match = text.match(reg);
|
|
if (match === null || match === void 0 ? void 0 : match.length) {
|
|
return text.replace(reg, callback(match[0]));
|
|
}
|
|
return text;
|
|
}
|
|
function slash(path) {
|
|
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
|
|
const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex
|
|
if (isExtendedLengthPath || hasNonAscii) {
|
|
return path;
|
|
}
|
|
return path.replace(/\\/g, '/');
|
|
}
|
|
//# sourceMappingURL=Formatter.js.map
|