396 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			396 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | Object.defineProperty(exports, '__esModule', { | ||
|  |   value: true | ||
|  | }); | ||
|  | Object.defineProperty(exports, 'Frame', { | ||
|  |   enumerable: true, | ||
|  |   get: function get() { | ||
|  |     return _types.Frame; | ||
|  |   } | ||
|  | }); | ||
|  | exports.separateMessageFromStack = exports.formatResultsErrors = exports.formatStackTrace = exports.getTopFrame = exports.getStackTraceLines = exports.formatExecError = void 0; | ||
|  | 
 | ||
|  | var _fs = _interopRequireDefault(require('fs')); | ||
|  | 
 | ||
|  | var _path = _interopRequireDefault(require('path')); | ||
|  | 
 | ||
|  | var _chalk = _interopRequireDefault(require('chalk')); | ||
|  | 
 | ||
|  | var _micromatch = _interopRequireDefault(require('micromatch')); | ||
|  | 
 | ||
|  | var _slash = _interopRequireDefault(require('slash')); | ||
|  | 
 | ||
|  | var _codeFrame = require('@babel/code-frame'); | ||
|  | 
 | ||
|  | var _stackUtils = _interopRequireDefault(require('stack-utils')); | ||
|  | 
 | ||
|  | var _types = require('./types'); | ||
|  | 
 | ||
|  | function _interopRequireDefault(obj) { | ||
|  |   return obj && obj.__esModule ? obj : {default: obj}; | ||
|  | } | ||
|  | 
 | ||
|  | var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; | ||
|  | 
 | ||
|  | var jestReadFile = | ||
|  |   global[Symbol.for('jest-native-read-file')] || _fs.default.readFileSync; | ||
|  | 
 | ||
|  | var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; | ||
|  | // stack utils tries to create pretty stack by making paths relative.
 | ||
|  | const stackUtils = new _stackUtils.default({ | ||
|  |   cwd: 'something which does not exist' | ||
|  | }); | ||
|  | let nodeInternals = []; | ||
|  | 
 | ||
|  | try { | ||
|  |   nodeInternals = _stackUtils.default.nodeInternals(); | ||
|  | } catch (e) { | ||
|  |   // `StackUtils.nodeInternals()` fails in browsers. We don't need to remove
 | ||
|  |   // node internals in the browser though, so no issue.
 | ||
|  | } | ||
|  | 
 | ||
|  | const PATH_NODE_MODULES = `${_path.default.sep}node_modules${ | ||
|  |   _path.default.sep | ||
|  | }`;
 | ||
|  | const PATH_JEST_PACKAGES = `${_path.default.sep}jest${ | ||
|  |   _path.default.sep | ||
|  | }packages${_path.default.sep}`; // filter for noisy stack trace lines
 | ||
|  | 
 | ||
|  | const JASMINE_IGNORE = /^\s+at(?:(?:.jasmine\-)|\s+jasmine\.buildExpectationResult)/; | ||
|  | const JEST_INTERNALS_IGNORE = /^\s+at.*?jest(-.*?)?(\/|\\)(build|node_modules|packages)(\/|\\)/; | ||
|  | const ANONYMOUS_FN_IGNORE = /^\s+at <anonymous>.*$/; | ||
|  | const ANONYMOUS_PROMISE_IGNORE = /^\s+at (new )?Promise \(<anonymous>\).*$/; | ||
|  | const ANONYMOUS_GENERATOR_IGNORE = /^\s+at Generator.next \(<anonymous>\).*$/; | ||
|  | const NATIVE_NEXT_IGNORE = /^\s+at next \(native\).*$/; | ||
|  | const TITLE_INDENT = '  '; | ||
|  | const MESSAGE_INDENT = '    '; | ||
|  | const STACK_INDENT = '      '; | ||
|  | const ANCESTRY_SEPARATOR = ' \u203A '; | ||
|  | 
 | ||
|  | const TITLE_BULLET = _chalk.default.bold('\u25cf '); | ||
|  | 
 | ||
|  | const STACK_TRACE_COLOR = _chalk.default.dim; | ||
|  | const STACK_PATH_REGEXP = /\s*at.*\(?(\:\d*\:\d*|native)\)?/; | ||
|  | const EXEC_ERROR_MESSAGE = 'Test suite failed to run'; | ||
|  | const NOT_EMPTY_LINE_REGEXP = /^(?!$)/gm; | ||
|  | 
 | ||
|  | const indentAllLines = (lines, indent) => | ||
|  |   lines.replace(NOT_EMPTY_LINE_REGEXP, indent); | ||
|  | 
 | ||
|  | const trim = string => (string || '').trim(); // Some errors contain not only line numbers in stack traces
 | ||
|  | // e.g. SyntaxErrors can contain snippets of code, and we don't
 | ||
|  | // want to trim those, because they may have pointers to the column/character
 | ||
|  | // which will get misaligned.
 | ||
|  | 
 | ||
|  | const trimPaths = string => | ||
|  |   string.match(STACK_PATH_REGEXP) ? trim(string) : string; | ||
|  | 
 | ||
|  | const getRenderedCallsite = (fileContent, line, column) => { | ||
|  |   let renderedCallsite = (0, _codeFrame.codeFrameColumns)( | ||
|  |     fileContent, | ||
|  |     { | ||
|  |       start: { | ||
|  |         column, | ||
|  |         line | ||
|  |       } | ||
|  |     }, | ||
|  |     { | ||
|  |       highlightCode: true | ||
|  |     } | ||
|  |   ); | ||
|  |   renderedCallsite = indentAllLines(renderedCallsite, MESSAGE_INDENT); | ||
|  |   renderedCallsite = `\n${renderedCallsite}\n`; | ||
|  |   return renderedCallsite; | ||
|  | }; // ExecError is an error thrown outside of the test suite (not inside an `it` or
 | ||
|  | // `before/after each` hooks). If it's thrown, none of the tests in the file
 | ||
|  | // are executed.
 | ||
|  | 
 | ||
|  | const formatExecError = (error, config, options, testPath, reuseMessage) => { | ||
|  |   if (!error || typeof error === 'number') { | ||
|  |     error = new Error(`Expected an Error, but "${String(error)}" was thrown`); | ||
|  |     error.stack = ''; | ||
|  |   } | ||
|  | 
 | ||
|  |   let message, stack; | ||
|  | 
 | ||
|  |   if (typeof error === 'string' || !error) { | ||
|  |     error || (error = 'EMPTY ERROR'); | ||
|  |     message = ''; | ||
|  |     stack = error; | ||
|  |   } else { | ||
|  |     message = error.message; | ||
|  |     stack = error.stack; | ||
|  |   } | ||
|  | 
 | ||
|  |   const separated = separateMessageFromStack(stack || ''); | ||
|  |   stack = separated.stack; | ||
|  | 
 | ||
|  |   if (separated.message.indexOf(trim(message)) !== -1) { | ||
|  |     // Often stack trace already contains the duplicate of the message
 | ||
|  |     message = separated.message; | ||
|  |   } | ||
|  | 
 | ||
|  |   message = indentAllLines(message, MESSAGE_INDENT); | ||
|  |   stack = | ||
|  |     stack && !options.noStackTrace | ||
|  |       ? '\n' + formatStackTrace(stack, config, options, testPath) | ||
|  |       : ''; | ||
|  | 
 | ||
|  |   if (message.match(/^\s*$/) && stack.match(/^\s*$/)) { | ||
|  |     // this can happen if an empty object is thrown.
 | ||
|  |     message = MESSAGE_INDENT + 'Error: No message was provided'; | ||
|  |   } | ||
|  | 
 | ||
|  |   let messageToUse; | ||
|  | 
 | ||
|  |   if (reuseMessage) { | ||
|  |     messageToUse = ` ${message.trim()}`; | ||
|  |   } else { | ||
|  |     messageToUse = `${EXEC_ERROR_MESSAGE}\n\n${message}`; | ||
|  |   } | ||
|  | 
 | ||
|  |   return TITLE_INDENT + TITLE_BULLET + messageToUse + stack + '\n'; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.formatExecError = formatExecError; | ||
|  | 
 | ||
|  | const removeInternalStackEntries = (lines, options) => { | ||
|  |   let pathCounter = 0; | ||
|  |   return lines.filter(line => { | ||
|  |     if (ANONYMOUS_FN_IGNORE.test(line)) { | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (ANONYMOUS_PROMISE_IGNORE.test(line)) { | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (ANONYMOUS_GENERATOR_IGNORE.test(line)) { | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (NATIVE_NEXT_IGNORE.test(line)) { | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (nodeInternals.some(internal => internal.test(line))) { | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!STACK_PATH_REGEXP.test(line)) { | ||
|  |       return true; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (JASMINE_IGNORE.test(line)) { | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (++pathCounter === 1) { | ||
|  |       return true; // always keep the first line even if it's from Jest
 | ||
|  |     } | ||
|  | 
 | ||
|  |     if (options.noStackTrace) { | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (JEST_INTERNALS_IGNORE.test(line)) { | ||
|  |       return false; | ||
|  |     } | ||
|  | 
 | ||
|  |     return true; | ||
|  |   }); | ||
|  | }; | ||
|  | 
 | ||
|  | const formatPaths = (config, relativeTestPath, line) => { | ||
|  |   // Extract the file path from the trace line.
 | ||
|  |   const match = line.match(/(^\s*at .*?\(?)([^()]+)(:[0-9]+:[0-9]+\)?.*$)/); | ||
|  | 
 | ||
|  |   if (!match) { | ||
|  |     return line; | ||
|  |   } | ||
|  | 
 | ||
|  |   let filePath = (0, _slash.default)( | ||
|  |     _path.default.relative(config.rootDir, match[2]) | ||
|  |   ); // highlight paths from the current test file
 | ||
|  | 
 | ||
|  |   if ( | ||
|  |     (config.testMatch && | ||
|  |       config.testMatch.length && | ||
|  |       _micromatch.default.some(filePath, config.testMatch)) || | ||
|  |     filePath === relativeTestPath | ||
|  |   ) { | ||
|  |     filePath = _chalk.default.reset.cyan(filePath); | ||
|  |   } | ||
|  | 
 | ||
|  |   return STACK_TRACE_COLOR(match[1]) + filePath + STACK_TRACE_COLOR(match[3]); | ||
|  | }; | ||
|  | 
 | ||
|  | const getStackTraceLines = ( | ||
|  |   stack, | ||
|  |   options = { | ||
|  |     noStackTrace: false | ||
|  |   } | ||
|  | ) => removeInternalStackEntries(stack.split(/\n/), options); | ||
|  | 
 | ||
|  | exports.getStackTraceLines = getStackTraceLines; | ||
|  | 
 | ||
|  | const getTopFrame = lines => { | ||
|  |   var _iteratorNormalCompletion = true; | ||
|  |   var _didIteratorError = false; | ||
|  |   var _iteratorError = undefined; | ||
|  | 
 | ||
|  |   try { | ||
|  |     for ( | ||
|  |       var _iterator = lines[Symbol.iterator](), _step; | ||
|  |       !(_iteratorNormalCompletion = (_step = _iterator.next()).done); | ||
|  |       _iteratorNormalCompletion = true | ||
|  |     ) { | ||
|  |       const line = _step.value; | ||
|  | 
 | ||
|  |       if ( | ||
|  |         line.includes(PATH_NODE_MODULES) || | ||
|  |         line.includes(PATH_JEST_PACKAGES) | ||
|  |       ) { | ||
|  |         continue; | ||
|  |       } | ||
|  | 
 | ||
|  |       const parsedFrame = stackUtils.parseLine(line.trim()); | ||
|  | 
 | ||
|  |       if (parsedFrame && parsedFrame.file) { | ||
|  |         return parsedFrame; | ||
|  |       } | ||
|  |     } | ||
|  |   } catch (err) { | ||
|  |     _didIteratorError = true; | ||
|  |     _iteratorError = err; | ||
|  |   } finally { | ||
|  |     try { | ||
|  |       if (!_iteratorNormalCompletion && _iterator.return != null) { | ||
|  |         _iterator.return(); | ||
|  |       } | ||
|  |     } finally { | ||
|  |       if (_didIteratorError) { | ||
|  |         throw _iteratorError; | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   return null; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.getTopFrame = getTopFrame; | ||
|  | 
 | ||
|  | const formatStackTrace = (stack, config, options, testPath) => { | ||
|  |   const lines = getStackTraceLines(stack, options); | ||
|  |   const topFrame = getTopFrame(lines); | ||
|  |   let renderedCallsite = ''; | ||
|  |   const relativeTestPath = testPath | ||
|  |     ? (0, _slash.default)(_path.default.relative(config.rootDir, testPath)) | ||
|  |     : null; | ||
|  | 
 | ||
|  |   if (topFrame) { | ||
|  |     const column = topFrame.column, | ||
|  |       filename = topFrame.file, | ||
|  |       line = topFrame.line; | ||
|  | 
 | ||
|  |     if (line && filename && _path.default.isAbsolute(filename)) { | ||
|  |       let fileContent; | ||
|  | 
 | ||
|  |       try { | ||
|  |         // TODO: check & read HasteFS instead of reading the filesystem:
 | ||
|  |         // see: https://github.com/facebook/jest/pull/5405#discussion_r164281696
 | ||
|  |         fileContent = jestReadFile(filename, 'utf8'); | ||
|  |         renderedCallsite = getRenderedCallsite(fileContent, line, column); | ||
|  |       } catch (e) { | ||
|  |         // the file does not exist or is inaccessible, we ignore
 | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   const stacktrace = lines | ||
|  |     .filter(Boolean) | ||
|  |     .map( | ||
|  |       line => | ||
|  |         STACK_INDENT + formatPaths(config, relativeTestPath, trimPaths(line)) | ||
|  |     ) | ||
|  |     .join('\n'); | ||
|  |   return `${renderedCallsite}\n${stacktrace}`; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.formatStackTrace = formatStackTrace; | ||
|  | 
 | ||
|  | const formatResultsErrors = (testResults, config, options, testPath) => { | ||
|  |   const failedResults = testResults.reduce((errors, result) => { | ||
|  |     result.failureMessages.forEach(content => | ||
|  |       errors.push({ | ||
|  |         content, | ||
|  |         result | ||
|  |       }) | ||
|  |     ); | ||
|  |     return errors; | ||
|  |   }, []); | ||
|  | 
 | ||
|  |   if (!failedResults.length) { | ||
|  |     return null; | ||
|  |   } | ||
|  | 
 | ||
|  |   return failedResults | ||
|  |     .map(({result, content}) => { | ||
|  |       let _separateMessageFromS = separateMessageFromStack(content), | ||
|  |         message = _separateMessageFromS.message, | ||
|  |         stack = _separateMessageFromS.stack; | ||
|  | 
 | ||
|  |       stack = options.noStackTrace | ||
|  |         ? '' | ||
|  |         : STACK_TRACE_COLOR( | ||
|  |             formatStackTrace(stack, config, options, testPath) | ||
|  |           ) + '\n'; | ||
|  |       message = indentAllLines(message, MESSAGE_INDENT); | ||
|  |       const title = | ||
|  |         _chalk.default.bold.red( | ||
|  |           TITLE_INDENT + | ||
|  |             TITLE_BULLET + | ||
|  |             result.ancestorTitles.join(ANCESTRY_SEPARATOR) + | ||
|  |             (result.ancestorTitles.length ? ANCESTRY_SEPARATOR : '') + | ||
|  |             result.title | ||
|  |         ) + '\n'; | ||
|  |       return title + '\n' + message + '\n' + stack; | ||
|  |     }) | ||
|  |     .join('\n'); | ||
|  | }; // jasmine and worker farm sometimes don't give us access to the actual
 | ||
|  | // Error object, so we have to regexp out the message from the stack string
 | ||
|  | // to format it.
 | ||
|  | 
 | ||
|  | exports.formatResultsErrors = formatResultsErrors; | ||
|  | 
 | ||
|  | const separateMessageFromStack = content => { | ||
|  |   if (!content) { | ||
|  |     return { | ||
|  |       message: '', | ||
|  |       stack: '' | ||
|  |     }; | ||
|  |   } // All lines up to what looks like a stack -- or if nothing looks like a stack
 | ||
|  |   // (maybe it's a code frame instead), just the first non-empty line.
 | ||
|  |   // If the error is a plain "Error:" instead of a SyntaxError or TypeError we
 | ||
|  |   // remove the prefix from the message because it is generally not useful.
 | ||
|  | 
 | ||
|  |   const messageMatch = content.match( | ||
|  |     /^(?:Error: )?([\s\S]*?(?=\n\s*at\s.*\:\d*\:\d*)|\s*.*)([\s\S]*)$/ | ||
|  |   ); | ||
|  | 
 | ||
|  |   if (!messageMatch) { | ||
|  |     // For flow
 | ||
|  |     throw new Error('If you hit this error, the regex above is buggy.'); | ||
|  |   } | ||
|  | 
 | ||
|  |   const message = messageMatch[1]; | ||
|  |   const stack = messageMatch[2]; | ||
|  |   return { | ||
|  |     message, | ||
|  |     stack | ||
|  |   }; | ||
|  | }; | ||
|  | 
 | ||
|  | exports.separateMessageFromStack = separateMessageFromStack; |