11cb0ef41Sopenharmony_ci'use strict';
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ciconst {
41cb0ef41Sopenharmony_ci  ArrayPrototypeIndexOf,
51cb0ef41Sopenharmony_ci  ArrayPrototypeJoin,
61cb0ef41Sopenharmony_ci  ArrayPrototypeMap,
71cb0ef41Sopenharmony_ci  ErrorPrototypeToString,
81cb0ef41Sopenharmony_ci  RegExpPrototypeSymbolSplit,
91cb0ef41Sopenharmony_ci  StringPrototypeRepeat,
101cb0ef41Sopenharmony_ci  StringPrototypeSlice,
111cb0ef41Sopenharmony_ci  StringPrototypeStartsWith,
121cb0ef41Sopenharmony_ci  SafeStringIterator,
131cb0ef41Sopenharmony_ci} = primordials;
141cb0ef41Sopenharmony_ci
151cb0ef41Sopenharmony_cilet debug = require('internal/util/debuglog').debuglog('source_map', (fn) => {
161cb0ef41Sopenharmony_ci  debug = fn;
171cb0ef41Sopenharmony_ci});
181cb0ef41Sopenharmony_ciconst { getStringWidth } = require('internal/util/inspect');
191cb0ef41Sopenharmony_ciconst { readFileSync } = require('fs');
201cb0ef41Sopenharmony_ciconst { findSourceMap } = require('internal/source_map/source_map_cache');
211cb0ef41Sopenharmony_ciconst {
221cb0ef41Sopenharmony_ci  kNoOverride,
231cb0ef41Sopenharmony_ci  overrideStackTrace,
241cb0ef41Sopenharmony_ci  maybeOverridePrepareStackTrace,
251cb0ef41Sopenharmony_ci  kIsNodeError,
261cb0ef41Sopenharmony_ci} = require('internal/errors');
271cb0ef41Sopenharmony_ciconst { fileURLToPath } = require('internal/url');
281cb0ef41Sopenharmony_ciconst { setGetSourceMapErrorSource } = internalBinding('errors');
291cb0ef41Sopenharmony_ci
301cb0ef41Sopenharmony_ci// Create a prettified stacktrace, inserting context from source maps
311cb0ef41Sopenharmony_ci// if possible.
321cb0ef41Sopenharmony_ciconst prepareStackTrace = (globalThis, error, trace) => {
331cb0ef41Sopenharmony_ci  // API for node internals to override error stack formatting
341cb0ef41Sopenharmony_ci  // without interfering with userland code.
351cb0ef41Sopenharmony_ci  // TODO(bcoe): add support for source-maps to repl.
361cb0ef41Sopenharmony_ci  if (overrideStackTrace.has(error)) {
371cb0ef41Sopenharmony_ci    const f = overrideStackTrace.get(error);
381cb0ef41Sopenharmony_ci    overrideStackTrace.delete(error);
391cb0ef41Sopenharmony_ci    return f(error, trace);
401cb0ef41Sopenharmony_ci  }
411cb0ef41Sopenharmony_ci
421cb0ef41Sopenharmony_ci  const globalOverride =
431cb0ef41Sopenharmony_ci    maybeOverridePrepareStackTrace(globalThis, error, trace);
441cb0ef41Sopenharmony_ci  if (globalOverride !== kNoOverride) return globalOverride;
451cb0ef41Sopenharmony_ci
461cb0ef41Sopenharmony_ci  let errorString;
471cb0ef41Sopenharmony_ci  if (kIsNodeError in error) {
481cb0ef41Sopenharmony_ci    errorString = `${error.name} [${error.code}]: ${error.message}`;
491cb0ef41Sopenharmony_ci  } else {
501cb0ef41Sopenharmony_ci    errorString = ErrorPrototypeToString(error);
511cb0ef41Sopenharmony_ci  }
521cb0ef41Sopenharmony_ci
531cb0ef41Sopenharmony_ci  if (trace.length === 0) {
541cb0ef41Sopenharmony_ci    return errorString;
551cb0ef41Sopenharmony_ci  }
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_ci  let lastSourceMap;
581cb0ef41Sopenharmony_ci  let lastFileName;
591cb0ef41Sopenharmony_ci  const preparedTrace = ArrayPrototypeJoin(ArrayPrototypeMap(trace, (t, i) => {
601cb0ef41Sopenharmony_ci    const str = i !== 0 ? '\n    at ' : '';
611cb0ef41Sopenharmony_ci    try {
621cb0ef41Sopenharmony_ci      // A stack trace will often have several call sites in a row within the
631cb0ef41Sopenharmony_ci      // same file, cache the source map and file content accordingly:
641cb0ef41Sopenharmony_ci      let fileName = t.getFileName();
651cb0ef41Sopenharmony_ci      if (fileName === undefined) {
661cb0ef41Sopenharmony_ci        fileName = t.getEvalOrigin();
671cb0ef41Sopenharmony_ci      }
681cb0ef41Sopenharmony_ci      const sm = fileName === lastFileName ?
691cb0ef41Sopenharmony_ci        lastSourceMap :
701cb0ef41Sopenharmony_ci        findSourceMap(fileName);
711cb0ef41Sopenharmony_ci      lastSourceMap = sm;
721cb0ef41Sopenharmony_ci      lastFileName = fileName;
731cb0ef41Sopenharmony_ci      if (sm) {
741cb0ef41Sopenharmony_ci        // Source Map V3 lines/columns start at 0/0 whereas stack traces
751cb0ef41Sopenharmony_ci        // start at 1/1:
761cb0ef41Sopenharmony_ci        const {
771cb0ef41Sopenharmony_ci          originalLine,
781cb0ef41Sopenharmony_ci          originalColumn,
791cb0ef41Sopenharmony_ci          originalSource,
801cb0ef41Sopenharmony_ci        } = sm.findEntry(t.getLineNumber() - 1, t.getColumnNumber() - 1);
811cb0ef41Sopenharmony_ci        if (originalSource && originalLine !== undefined &&
821cb0ef41Sopenharmony_ci            originalColumn !== undefined) {
831cb0ef41Sopenharmony_ci          const name = getOriginalSymbolName(sm, trace, i);
841cb0ef41Sopenharmony_ci          // Construct call site name based on: v8.dev/docs/stack-trace-api:
851cb0ef41Sopenharmony_ci          const fnName = t.getFunctionName() ?? t.getMethodName();
861cb0ef41Sopenharmony_ci          const typeName = t.getTypeName();
871cb0ef41Sopenharmony_ci          const namePrefix = typeName !== null && typeName !== 'global' ? `${typeName}.` : '';
881cb0ef41Sopenharmony_ci          const originalName = `${namePrefix}${fnName || '<anonymous>'}`;
891cb0ef41Sopenharmony_ci          // The original call site may have a different symbol name
901cb0ef41Sopenharmony_ci          // associated with it, use it:
911cb0ef41Sopenharmony_ci          const prefix = (name && name !== originalName) ?
921cb0ef41Sopenharmony_ci            `${name}` :
931cb0ef41Sopenharmony_ci            `${originalName}`;
941cb0ef41Sopenharmony_ci          const hasName = !!(name || originalName);
951cb0ef41Sopenharmony_ci          const originalSourceNoScheme =
961cb0ef41Sopenharmony_ci            StringPrototypeStartsWith(originalSource, 'file://') ?
971cb0ef41Sopenharmony_ci              fileURLToPath(originalSource) : originalSource;
981cb0ef41Sopenharmony_ci          // Replace the transpiled call site with the original:
991cb0ef41Sopenharmony_ci          return `${str}${prefix}${hasName ? ' (' : ''}` +
1001cb0ef41Sopenharmony_ci            `${originalSourceNoScheme}:${originalLine + 1}:` +
1011cb0ef41Sopenharmony_ci            `${originalColumn + 1}${hasName ? ')' : ''}`;
1021cb0ef41Sopenharmony_ci        }
1031cb0ef41Sopenharmony_ci      }
1041cb0ef41Sopenharmony_ci    } catch (err) {
1051cb0ef41Sopenharmony_ci      debug(err);
1061cb0ef41Sopenharmony_ci    }
1071cb0ef41Sopenharmony_ci    return `${str}${t}`;
1081cb0ef41Sopenharmony_ci  }), '');
1091cb0ef41Sopenharmony_ci  return `${errorString}\n    at ${preparedTrace}`;
1101cb0ef41Sopenharmony_ci};
1111cb0ef41Sopenharmony_ci
1121cb0ef41Sopenharmony_ci// Transpilers may have removed the original symbol name used in the stack
1131cb0ef41Sopenharmony_ci// trace, if possible restore it from the names field of the source map:
1141cb0ef41Sopenharmony_cifunction getOriginalSymbolName(sourceMap, trace, curIndex) {
1151cb0ef41Sopenharmony_ci  // First check for a symbol name associated with the enclosing function:
1161cb0ef41Sopenharmony_ci  const enclosingEntry = sourceMap.findEntry(
1171cb0ef41Sopenharmony_ci    trace[curIndex].getEnclosingLineNumber() - 1,
1181cb0ef41Sopenharmony_ci    trace[curIndex].getEnclosingColumnNumber() - 1,
1191cb0ef41Sopenharmony_ci  );
1201cb0ef41Sopenharmony_ci  if (enclosingEntry.name) return enclosingEntry.name;
1211cb0ef41Sopenharmony_ci  // Fallback to using the symbol name attached to the next stack frame:
1221cb0ef41Sopenharmony_ci  const currentFileName = trace[curIndex].getFileName();
1231cb0ef41Sopenharmony_ci  const nextCallSite = trace[curIndex + 1];
1241cb0ef41Sopenharmony_ci  if (nextCallSite && currentFileName === nextCallSite.getFileName()) {
1251cb0ef41Sopenharmony_ci    const { name } = sourceMap.findEntry(
1261cb0ef41Sopenharmony_ci      nextCallSite.getLineNumber() - 1,
1271cb0ef41Sopenharmony_ci      nextCallSite.getColumnNumber() - 1,
1281cb0ef41Sopenharmony_ci    );
1291cb0ef41Sopenharmony_ci    return name;
1301cb0ef41Sopenharmony_ci  }
1311cb0ef41Sopenharmony_ci}
1321cb0ef41Sopenharmony_ci
1331cb0ef41Sopenharmony_ci// Places a snippet of code from where the exception was originally thrown
1341cb0ef41Sopenharmony_ci// above the stack trace. This logic is modeled after GetErrorSource in
1351cb0ef41Sopenharmony_ci// node_errors.cc.
1361cb0ef41Sopenharmony_cifunction getErrorSource(
1371cb0ef41Sopenharmony_ci  sourceMap,
1381cb0ef41Sopenharmony_ci  originalSourcePath,
1391cb0ef41Sopenharmony_ci  originalLine,
1401cb0ef41Sopenharmony_ci  originalColumn,
1411cb0ef41Sopenharmony_ci) {
1421cb0ef41Sopenharmony_ci  const originalSourcePathNoScheme =
1431cb0ef41Sopenharmony_ci    StringPrototypeStartsWith(originalSourcePath, 'file://') ?
1441cb0ef41Sopenharmony_ci      fileURLToPath(originalSourcePath) : originalSourcePath;
1451cb0ef41Sopenharmony_ci  const source = getOriginalSource(
1461cb0ef41Sopenharmony_ci    sourceMap.payload,
1471cb0ef41Sopenharmony_ci    originalSourcePath,
1481cb0ef41Sopenharmony_ci  );
1491cb0ef41Sopenharmony_ci  if (typeof source !== 'string') {
1501cb0ef41Sopenharmony_ci    return;
1511cb0ef41Sopenharmony_ci  }
1521cb0ef41Sopenharmony_ci  const lines = RegExpPrototypeSymbolSplit(/\r?\n/, source, originalLine + 1);
1531cb0ef41Sopenharmony_ci  const line = lines[originalLine];
1541cb0ef41Sopenharmony_ci  if (!line) {
1551cb0ef41Sopenharmony_ci    return;
1561cb0ef41Sopenharmony_ci  }
1571cb0ef41Sopenharmony_ci
1581cb0ef41Sopenharmony_ci  // Display ^ in appropriate position, regardless of whether tabs or
1591cb0ef41Sopenharmony_ci  // spaces are used:
1601cb0ef41Sopenharmony_ci  let prefix = '';
1611cb0ef41Sopenharmony_ci  for (const character of new SafeStringIterator(
1621cb0ef41Sopenharmony_ci    StringPrototypeSlice(line, 0, originalColumn + 1))) {
1631cb0ef41Sopenharmony_ci    prefix += character === '\t' ? '\t' :
1641cb0ef41Sopenharmony_ci      StringPrototypeRepeat(' ', getStringWidth(character));
1651cb0ef41Sopenharmony_ci  }
1661cb0ef41Sopenharmony_ci  prefix = StringPrototypeSlice(prefix, 0, -1); // The last character is '^'.
1671cb0ef41Sopenharmony_ci
1681cb0ef41Sopenharmony_ci  const exceptionLine =
1691cb0ef41Sopenharmony_ci   `${originalSourcePathNoScheme}:${originalLine + 1}\n${line}\n${prefix}^\n\n`;
1701cb0ef41Sopenharmony_ci  return exceptionLine;
1711cb0ef41Sopenharmony_ci}
1721cb0ef41Sopenharmony_ci
1731cb0ef41Sopenharmony_cifunction getOriginalSource(payload, originalSourcePath) {
1741cb0ef41Sopenharmony_ci  let source;
1751cb0ef41Sopenharmony_ci  // payload.sources has been normalized to be an array of absolute urls.
1761cb0ef41Sopenharmony_ci  const sourceContentIndex =
1771cb0ef41Sopenharmony_ci    ArrayPrototypeIndexOf(payload.sources, originalSourcePath);
1781cb0ef41Sopenharmony_ci  if (payload.sourcesContent?.[sourceContentIndex]) {
1791cb0ef41Sopenharmony_ci    // First we check if the original source content was provided in the
1801cb0ef41Sopenharmony_ci    // source map itself:
1811cb0ef41Sopenharmony_ci    source = payload.sourcesContent[sourceContentIndex];
1821cb0ef41Sopenharmony_ci  } else if (StringPrototypeStartsWith(originalSourcePath, 'file://')) {
1831cb0ef41Sopenharmony_ci    // If no sourcesContent was found, attempt to load the original source
1841cb0ef41Sopenharmony_ci    // from disk:
1851cb0ef41Sopenharmony_ci    debug(`read source of ${originalSourcePath} from filesystem`);
1861cb0ef41Sopenharmony_ci    const originalSourcePathNoScheme = fileURLToPath(originalSourcePath);
1871cb0ef41Sopenharmony_ci    try {
1881cb0ef41Sopenharmony_ci      source = readFileSync(originalSourcePathNoScheme, 'utf8');
1891cb0ef41Sopenharmony_ci    } catch (err) {
1901cb0ef41Sopenharmony_ci      debug(err);
1911cb0ef41Sopenharmony_ci    }
1921cb0ef41Sopenharmony_ci  }
1931cb0ef41Sopenharmony_ci  return source;
1941cb0ef41Sopenharmony_ci}
1951cb0ef41Sopenharmony_ci
1961cb0ef41Sopenharmony_cifunction getSourceMapErrorSource(fileName, lineNumber, columnNumber) {
1971cb0ef41Sopenharmony_ci  const sm = findSourceMap(fileName);
1981cb0ef41Sopenharmony_ci  if (sm === undefined) {
1991cb0ef41Sopenharmony_ci    return;
2001cb0ef41Sopenharmony_ci  }
2011cb0ef41Sopenharmony_ci  const {
2021cb0ef41Sopenharmony_ci    originalLine,
2031cb0ef41Sopenharmony_ci    originalColumn,
2041cb0ef41Sopenharmony_ci    originalSource,
2051cb0ef41Sopenharmony_ci  } = sm.findEntry(lineNumber - 1, columnNumber);
2061cb0ef41Sopenharmony_ci  const errorSource = getErrorSource(sm, originalSource, originalLine, originalColumn);
2071cb0ef41Sopenharmony_ci  return errorSource;
2081cb0ef41Sopenharmony_ci}
2091cb0ef41Sopenharmony_ci
2101cb0ef41Sopenharmony_cisetGetSourceMapErrorSource(getSourceMapErrorSource);
2111cb0ef41Sopenharmony_ci
2121cb0ef41Sopenharmony_cimodule.exports = {
2131cb0ef41Sopenharmony_ci  prepareStackTrace,
2141cb0ef41Sopenharmony_ci};
215