--[[
	ROBLOX deviation: code not ported
	original code:
function checkForCommonEnvironmentErrors(error: string) {
  if (
    error.includes('ReferenceError: document is not defined') ||
    error.includes('ReferenceError: window is not defined') ||
    error.includes('ReferenceError: navigator is not defined')
  ) {
    return warnAboutWrongTestEnvironment(error, 'jsdom');
  } else if (error.includes('.unref is not a function')) {
    return warnAboutWrongTestEnvironment(error, 'node');
  }

  return error;
}

function warnAboutWrongTestEnvironment(error: string, env: 'jsdom' | 'node') {
  return (
    chalk.bold.red(
      `The error below may be caused by using the wrong test environment, see ${chalk.dim.underline(
        'https://jestjs.io/docs/configuration#testenvironment-string',
      )}.\nConsider using the "${env}" test environment.\n\n`,
    ) + error
  );
}

// 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.
export const formatExecError = (
  error: Error | TestResult.SerializableError | string | undefined,
  config: StackTraceConfig,
  options: StackTraceOptions,
  testPath?: Path,
  reuseMessage?: boolean,
): string => {
  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 =
      typeof error.stack === 'string'
        ? error.stack
        : `thrown: ${prettyFormat(error, {maxDepth: 3})}`;
  }

  const separated = separateMessageFromStack(stack || '');
  stack = separated.stack;

  if (separated.message.includes(trim(message))) {
    // Often stack trace already contains the duplicate of the message
    message = separated.message;
  }

  message = checkForCommonEnvironmentErrors(message);

  message = indentAllLines(message, MESSAGE_INDENT);

  stack =
    stack && !options.noStackTrace
      ? '\n' + formatStackTrace(stack, config, options, testPath)
      : '';

  if (
    typeof stack !== 'string' ||
    (blankStringRegexp.test(message) && blankStringRegexp.test(stack))
  ) {
    // this can happen if an empty object is thrown.
    message = `thrown: ${prettyFormat(error, {maxDepth: 3})}`;
  }

  let messageToUse;

  if (reuseMessage) {
    messageToUse = ` ${message.trim()}`;
  } else {
    messageToUse = `${EXEC_ERROR_MESSAGE}\n\n${message}`;
  }

  return TITLE_INDENT + TITLE_BULLET + messageToUse + stack + '\n';
};
]]

local function removeInternalStackEntries(lines: { string }, options: StackTraceOptions): { string }
	local pathCounter = 0

	return Array.filter(lines, function(line)
		if line:find(ANONYMOUS_FN_IGNORE) then
			return false
		end

		if line:find(ANONYMOUS_PROMISE_IGNORE) or line:find(ANONYMOUS_NEW_PROMISE_IGNORE) then
			return false
		end

		if line:find(ANONYMOUS_GENERATOR_IGNORE) then
			return false
		end

		if line:find(NATIVE_NEXT_IGNORE) then
			return false
		end

		-- ROBLOX deviation: omitting node specific stuff
		-- if (nodeInternals.some(internal => internal.test(line))) {
		-- 	return false;
		-- }

		-- ROBLOX deviation: match both since Lua doesn't have alternations
		if not line:find(STACK_PATH_REGEXP) or not line:find(STACK_PATH_REGEXP_NATIVE) then
			return true
		end

		-- ROBLOX deviation: match both since Lua doesn't have alternations
		if line:find(JASMINE_IGNORE) or line:find(JASMINE_IGNORE_BUILDEXPECTATIONRESULT) then
			return false
		end

		pathCounter += 1
		if pathCounter == 1 then
			return true -- always keep the first line even if it's from Jest
		end

		if options.noStackTrace then
			return false
		end

		-- if (JEST_INTERNALS_IGNORE.test(line)) {
		-- 	return false;
		-- }

		return true
	end)
end