add: unit and integration testing

This commit is contained in:
odweta
2026-09-07 18:09:36 +02:00
parent 76fde42334
commit 52a25aeaf5
7230 changed files with 561560 additions and 135112 deletions
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Copyright Contributors to the Jest project.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+59
View File
@@ -0,0 +1,59 @@
# @jest/source-map
Applies source maps to stack traces, so a failing test points at the code you wrote rather than at the code Jest ran.
This is a module used internally by Jest. It exists because `--enable-source-maps` and `module.setSourceMapsSupport()` do not cover code compiled through `vm`, which is how Jest evaluates test files, and because Node offers no way to register a map for a filename — Jest serves maps from its own transform pipeline rather than from `sourceMappingURL` comments on disk.
## Install
```sh
$ npm install --save @jest/source-map
```
## API
### `SourceMapSupport#install(sourceMaps?: SourceMapRegistry | null, options?: SourceMapSupportInstallOptions): void`
Replaces `Error.prepareStackTrace` in the current realm, so reading `.stack` on any error renders frames against the original sources. `jest-runner` holds one instance per worker and installs once per test file.
`sourceMaps` maps a transformed file to the `.map` file Jest wrote into its transform cache — inside Jest, `runtime.getSourceMaps()`. The `sources` inside a map resolve with URL semantics against the transformed file, not against the map's own location. Files missing from the registry fall back to a `sourceMappingURL` comment on the file itself, which covers pre-compiled output that ships its own map.
```javascript
import {SourceMapSupport} from '@jest/source-map';
const sourceMapSupport = new SourceMapSupport();
sourceMapSupport.install(new Map([['/build/app.js', '/cache/app.js.map']]));
new Error('boom').stack;
// Error: boom
// at greet (/src/app.ts:12:9)
```
The formatter stays installed for the lifetime of the process, and each call swaps in a new registry. Nothing is restored, deliberately: an error thrown after a test finishes — a stray timer, a floating promise — is the one users have the hardest time placing, and it would otherwise report a position in the transformed file.
A map that cannot be parsed is reported once via `console.warn`; pass `{suppressWarnings: true}` to turn that off.
### `SourceMapSupport#getCallsite(level: number, sourceMaps?: SourceMapRegistry | null): CallSite`
Returns a single remapped [`CallSite`](https://v8.dev/docs/stack-trace-api#customizing-stack-traces), `level` frames above the caller. Used for `--testLocationInResults`. Shares its parsed maps with the installed formatter, so each `.map` file is read once.
### `getCallsite(level: number, sourceMaps?: SourceMapRegistry | null): CallSite`
Deprecated free-function alias of `SourceMapSupport#getCallsite`.
### `SourceMapRegistry`
`Map<string, string>` — transformed file path to source map path.
## Function names are chosen for readability, not for the spec
A frame is named after the source map's `name` at the frame's **own** position. That name is the identifier being _called_ there rather than the enclosing function, so every frame gets annotated with the call on its line:
```
at Object.toBeTruthy (assertionCount.test.js:12:17)
at Object.setTimeout (inside.js:9:3)
```
The spec-correct reading takes the name from the _caller's_ position instead. It is what [`source-map-support@0.5.14`](https://github.com/evanw/node-source-map-support/pull/253) switched to, and it collapses the frames above to `Object.<anonymous>`, because V8 has no name of its own for a module-level frame. Positions are identical either way — only names differ, and only where V8 could not name the frame.
This package optimises for reading a test failure, so it keeps the annotation. If you need the spec semantics, `getCallsite` hands back a `CallSite` you can map yourself.
+59
View File
@@ -0,0 +1,59 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import callsites from 'callsites';
/**
* One remapped `CallSite`, `level` frames above the caller.
*
* @deprecated Use `SourceMapSupport#getCallsite` instead.
*/
export declare function getCallsite(
level: number,
sourceMaps?: SourceMapRegistry | null,
): callsites.CallSite;
/** Transformed file path to the map file written for it. */
export declare type SourceMapRegistry = ReadonlyMap<string, string>;
export declare class SourceMapSupport {
private activeCache;
private nullCache;
private readonly cachesByRegistry;
private suppressWarnings;
private readonly reportedMapPaths;
private readonly boundFormatStackTrace;
constructor();
/**
* Replaces `Error.prepareStackTrace` in the current realm, so `error.stack`
* renders frames against the original sources.
*
* Stays installed for the lifetime of the worker — each call swaps in its
* own cache. There is deliberately no `uninstall`: restoring V8's formatter
* at teardown would leave an error thrown after the environment is torn
* down pointing into the transformed file. Holding the cache does not
* retain the environment: it only ever references path strings and parsed
* source maps.
*/
install(
sourceMaps?: SourceMapRegistry | null,
options?: SourceMapSupportInstallOptions,
): void;
/** One remapped `CallSite`, `level` frames above the caller. */
getCallsite(
level: number,
sourceMaps?: SourceMapRegistry | null,
): callsites.CallSite;
private cacheFor;
private reportUnparsable;
private formatStackTrace;
}
export declare interface SourceMapSupportInstallOptions {
/** Turns off the once-per-map warning about maps that cannot be parsed. */
suppressWarnings?: boolean;
}
+791
View File
@@ -0,0 +1,791 @@
/*!
* /**
* * Copyright (c) Meta Platforms, Inc. and affiliates.
* *
* * This source code is licensed under the MIT license found in the
* * LICENSE file in the root directory of this source tree.
* * /
*/
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/SourceMapCache.ts"
(__unused_webpack_module, exports) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.SourceMapCache = void 0;
exports.mapSourcePosition = mapSourcePosition;
function _traceMapping() {
const data = require("@jridgewell/trace-mapping");
_traceMapping = function () {
return data;
};
return data;
}
function _convertSourceMap() {
const data = require("convert-source-map");
_convertSourceMap = function () {
return data;
};
return data;
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// The *last* sourceMappingURL wins, so one inside a comment or a string does
// not shadow the real one.
function findSourceMapUrl(fileContent) {
const lastMatch = [...fileContent.matchAll(_convertSourceMap().mapFileCommentRegex)].at(-1);
return lastMatch == null ? null : lastMatch[1] ?? lastMatch[2] ?? null;
}
function resolveUrl(url, base) {
try {
return new URL(url, base).href;
} catch {
return null;
}
}
// Map paths are content-addressed, so a stale one is never asked for again and
// a parsed map can live for the whole process. The base URL joins the key
// because it is baked into the resolved sources, and a transformer with its
// own `getCacheKey` can hand two generated files the same map path.
// Nothing evicts these, so a worker ends up holding one parsed map per file it
// has formatted a stack for. That is the point: re-reading and re-parsing on
// every frame of every stack costs far more than keeping them.
const parsedByCachePath = new Map();
// A worker moves on to the next test file while a stray timer from the
// previous one can still throw; remembering where each file's map lived keeps
// those frames resolvable. `null` marks a file transformed more than one way —
// as ESM and as CJS, say — where a frame does not say which map it came from,
// so decline rather than guess.
// This also grows for the worker's lifetime, holding two path strings per file
// — small enough that bounding it would cost more than it saves, and a frame
// arriving after its file's registry is gone has nowhere else to look.
const rememberedMapPaths = new Map();
function rememberMapPath(generatedPath, sourceMapPath) {
const remembered = rememberedMapPaths.get(generatedPath);
rememberedMapPaths.set(generatedPath, remembered === undefined || remembered === sourceMapPath ? sourceMapPath : null);
}
// `mapUrl` is what the map's `sources` resolve against.
function parseMap(content, mapUrl) {
try {
// `AnyMap` rather than `TraceMap`, which throws on the indexed maps that
// bundlers emit as a top-level `sections` array.
return (0, _traceMapping().AnyMap)(content, mapUrl);
} catch {
return null;
}
}
class SourceMapCache {
sourceMaps;
reader;
reportUnparsable;
loaded = new Map();
attempted = new Map();
constructor(sourceMaps, reader, reportUnparsable) {
this.sourceMaps = sourceMaps;
this.reader = reader;
this.reportUnparsable = reportUnparsable;
}
get(generatedPath) {
const cached = this.loaded.get(generatedPath);
const registered = this.sourceMaps?.get(generatedPath);
// The registry fills in lazily, so a file looked up before its map was
// registered must not stay unmapped. Retry only when the entry points
// somewhere new: a map that failed to load would otherwise be re-read for
// every frame, and the runtime empties the registry at teardown while
// stacks are still being formatted — an entry going away must not drop a
// loaded map.
const isStale = registered != null && this.attempted.get(generatedPath) !== registered;
if (cached !== undefined && !isStale) {
return cached;
}
const loaded = this.load(generatedPath);
this.loaded.set(generatedPath, loaded);
this.attempted.set(generatedPath, registered);
return loaded;
}
// A mapped source comes back as a URL, which is not what a stack frame should
// show for a file on disk.
toDisplayPath(url) {
return this.reader.toPath(url);
}
// Formatting a stack remembers a file's map as a side effect, but a file no
// stack ever mentioned is never recorded that way. Called before this cache
// stops being the active one, so a late frame from such a file still finds
// its map. The same ambiguity rule applies: a file already remembered with a
// different map path flips to `null` and the fallback declines.
rememberAll() {
if (this.sourceMaps == null) {
return;
}
for (const [generatedPath, sourceMapPath] of this.sourceMaps) {
if (sourceMapPath !== '') {
rememberMapPath(generatedPath, sourceMapPath);
}
}
}
load(generatedPath) {
// The map Jest itself produced while transforming the file.
const registered = this.sourceMaps?.get(generatedPath);
if (registered != null && registered !== '') {
rememberMapPath(generatedPath, registered);
}
const sourceMapPath = registered != null && registered !== '' ? registered : rememberedMapPaths.get(generatedPath);
if (sourceMapPath != null && sourceMapPath !== '') {
return this.parseRegisteredMap(sourceMapPath, generatedPath);
}
return this.readAdjacent(generatedPath);
}
parseRegisteredMap(sourceMapPath, generatedPath) {
// Jest's transform writes the map to its cache directory, but the sources
// it names are relative to the file that was transformed.
const mapUrl = this.reader.toUrl(generatedPath);
const cacheKey = `${sourceMapPath}\0${mapUrl}`;
const cached = parsedByCachePath.get(cacheKey);
if (cached !== undefined) {
return cached;
}
const content = this.reader.read(sourceMapPath);
const map = content == null ? null : parseMap(content, mapUrl);
if (content != null && map == null) {
this.reportUnparsable(sourceMapPath, generatedPath);
}
parsedByCachePath.set(cacheKey, map);
return map;
}
// A `sourceMappingURL` comment on a file Jest did not transform, which covers
// pre-compiled output shipping its own map.
readAdjacent(generatedPath) {
const fileContent = this.reader.read(generatedPath);
// A frame can name a `data:` URL — a dynamic `import()` of one — and what
// that decodes to is not a file a `sourceMappingURL` comment sits on.
if (typeof fileContent !== 'string') {
return null;
}
const sourceMapUrl = findSourceMapUrl(fileContent);
if (sourceMapUrl == null) {
return null;
}
const generatedUrl = this.reader.toUrl(generatedPath);
const resolvedUrl = resolveUrl(sourceMapUrl, generatedUrl);
if (resolvedUrl == null) {
return null;
}
const isInline = resolvedUrl.startsWith('data:');
const content = this.reader.read(resolvedUrl);
if (content == null) {
// A `data:` URL that fails to decode is a broken inline map; a missing
// `.map` file next to the code is not worth reporting.
if (isInline) {
this.reportUnparsable(generatedPath, generatedPath);
}
return null;
}
// An inline map's sources are relative to the file carrying it. Every other
// map resolves them against wherever the map itself lives.
const mapUrl = isInline ? generatedUrl : resolvedUrl;
const map = parseMap(content, mapUrl);
if (map == null) {
this.reportUnparsable(isInline ? generatedPath : this.reader.toPath(resolvedUrl), generatedPath);
}
return map;
}
}
// Returns the position unchanged when nothing maps to it: a precise location
// in the compiled file beats a vague one in the original.
exports.SourceMapCache = SourceMapCache;
function mapSourcePosition(cache, position) {
const {
column,
line,
source
} = position;
// The tracer throws on out-of-range needles, and a throw inside
// `prepareStackTrace` replaces the whole stack with the exception.
if (column == null || line == null || column < 0 || line < 1) {
return position;
}
const map = cache.get(source);
if (map == null) {
return position;
}
const originalPosition = (0, _traceMapping().originalPositionFor)(map, {
column,
line
});
if (originalPosition.source == null) {
return position;
}
return {
column: originalPosition.column,
line: originalPosition.line,
name: originalPosition.name,
source: cache.toDisplayPath(originalPosition.source)
};
}
/***/ },
/***/ "./src/SourceMapSupport.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.addSourceMapConsumer = exports.SourceMapSupport = void 0;
function _traceMapping() {
const data = require("@jridgewell/trace-mapping");
_traceMapping = function () {
return data;
};
return data;
}
function _callsites() {
const data = _interopRequireDefault(require("callsites"));
_callsites = function () {
return data;
};
return data;
}
var _SourceMapCache = __webpack_require__("./src/SourceMapCache.ts");
var _nodeFileReader = __webpack_require__("./src/nodeFileReader.ts");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// TODO: replace with `util.getCallSites()`, whose `columnNumber` landed in
// Node 22.14 — the floor is still 18.
// Copied from https://github.com/rexxars/sourcemap-decorate-callsites/blob/5b9735a156964973a75dc62fd2c7f0c1975458e8/lib/index.js#L113-L158
const addSourceMapConsumer = (callsite, tracer) => {
const getLineNumber = callsite.getLineNumber.bind(callsite);
const getColumnNumber = callsite.getColumnNumber.bind(callsite);
let position = null;
function getPosition() {
if (position != null) {
return position;
}
// The needle is zero-based while V8 counts columns from one, so looking up
// V8's number directly finds the segment one column to the right.
const line = getLineNumber();
const column = (getColumnNumber() ?? 1) - 1;
// The tracer throws on out-of-range needles.
position = line == null || line < 1 || column < 0 ? {
column: null,
line: null,
name: null,
source: null
} : (0, _traceMapping().originalPositionFor)(tracer, {
column,
line
});
return position;
}
Object.defineProperties(callsite, {
getColumnNumber: {
value() {
// TODO: return `column + 1` in Jest 31, so this matches V8 and
// jest-circus. Reported zero-based until then, which is what
// `--testLocationInResults` documents for jest-jasmine2, and changing
// it breaks anyone reading that field. An unmapped position falls back
// to V8's one-based column, as it always has — the Jest 31 change
// turns that fallback consistent instead of one off.
const {
column
} = getPosition();
return column ?? getColumnNumber();
},
writable: false
},
getLineNumber: {
value() {
const {
line
} = getPosition();
return line ?? getLineNumber();
},
writable: false
}
});
};
// V8 treats an empty name as no name at all. Every fallback below has to keep
// doing the same, which is why none of them can become `??`.
exports.addSourceMapConsumer = addSourceMapConsumer;
function isPresent(value) {
return value != null && value !== '';
}
function orAnonymous(value) {
return isPresent(value) ? value : '<anonymous>';
}
function frameToString(frame) {
return frame.toString();
}
// Copied almost verbatim from the V8 source, by way of `source-map-support`.
// Every non-native frame is rendered through this rather than through V8's own
// `CallSite#toString`, so it decides the exact shape of every stack frame Jest
// prints.
function callSiteToString() {
let fileLocation = '';
let fileName;
if (this.isNative()) {
fileLocation = 'native';
} else {
fileName = this.getScriptNameOrSourceURL();
if (!isPresent(fileName) && this.isEval()) {
fileLocation = `${this.getEvalOrigin() ?? ''}, `;
}
// Source code does not originate from a file and is not native, but we can
// still get the source position inside the source string, e.g. in an eval
// string.
fileLocation += orAnonymous(fileName);
const lineNumber = this.getLineNumber();
if (lineNumber != null) {
fileLocation += `:${lineNumber}`;
const columnNumber = this.getColumnNumber();
if (columnNumber != null && columnNumber !== 0) {
fileLocation += `:${columnNumber}`;
}
}
}
let line = '';
const functionName = this.getFunctionName();
const isConstructor = this.isConstructor();
const isMethodCall = !(this.isToplevel() || isConstructor);
if (isMethodCall) {
const typeName = this.getTypeName();
const methodName = this.getMethodName();
if (isPresent(functionName)) {
if (isPresent(typeName) && !functionName.startsWith(typeName)) {
line += `${typeName}.`;
}
line += functionName;
// Skips the suffix when the function name already is the method name:
// both sides are -1 then.
if (isPresent(methodName) && functionName.indexOf(`.${methodName}`) !== functionName.length - methodName.length - 1) {
line += ` [as ${methodName}]`;
}
} else {
line += `${String(typeName)}.${orAnonymous(methodName)}`;
}
} else if (isConstructor) {
line += `new ${orAnonymous(functionName)}`;
} else if (isPresent(functionName)) {
line += functionName;
} else {
return fileLocation;
}
return `${line} (${fileLocation})`;
}
function cloneCallSite(frame) {
const clone = {};
const source = frame;
for (const name of Object.getOwnPropertyNames(Object.getPrototypeOf(frame))) {
// Resolved at call time rather than now, so that accessors on the
// prototype are not invoked here.
clone[name] = /^(?:is|get)/.test(name) ? () => source[name].call(frame) : source[name];
}
clone.toString = callSiteToString;
return clone;
}
function mapEvalOrigin(cache, origin) {
// Most eval() calls are in this format
const topLevel = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(origin);
if (topLevel) {
const position = (0, _SourceMapCache.mapSourcePosition)(cache, {
column: Number(topLevel[4]) - 1,
line: Number(topLevel[3]),
source: topLevel[2]
});
return `eval at ${topLevel[1]} (${position.source}:${position.line}:${(position.column ?? 0) + 1})`;
}
// Parse nested eval() calls using recursion
const nested = /^eval at ([^(]+) \((.+)\)$/.exec(origin);
if (nested) {
return `eval at ${nested[1]} (${mapEvalOrigin(cache, nested[2])})`;
}
// Make sure we still return useful information if we didn't find anything
return origin;
}
function wrapCallSite(cache, frame) {
if (frame.isNative()) {
return frame;
}
// Most call sites will return the source file from `getFileName()`, but code
// passed to eval() ending in "//# sourceURL=..." will return the source file
// from `getScriptNameOrSourceURL()` instead.
const fileName = frame.getFileName();
const source = isPresent(fileName) ? fileName : frame.getScriptNameOrSourceURL();
if (isPresent(source)) {
const column = frame.getColumnNumber();
const position = (0, _SourceMapCache.mapSourcePosition)(cache, {
column: column == null ? null : column - 1,
line: frame.getLineNumber(),
source
});
const mapped = cloneCallSite(frame);
const originalGetFunctionName = mapped.getFunctionName.bind(mapped);
// Deliberately the name at the frame's *own* mapped position, which is the
// identifier being called there rather than the enclosing function. It
// annotates each frame with the call on that line — `at Object.toBeTruthy
// (assertionCount.test.js:12:17)` — which is what makes a failing
// assertion's stack readable. Taking the caller's position instead would be
// the spec-correct reading and would collapse these to `Object.<anonymous>`.
mapped.getFunctionName = () => isPresent(position.name) ? position.name : originalGetFunctionName();
mapped.getFileName = () => position.source;
mapped.getLineNumber = () => position.line;
mapped.getColumnNumber = () => position.column == null ? null : position.column + 1;
mapped.getScriptNameOrSourceURL = () => position.source;
return mapped;
}
// Code called using eval() needs special handling
const origin = frame.isEval() ? frame.getEvalOrigin() : undefined;
if (isPresent(origin)) {
const mapped = cloneCallSite(frame);
const mappedOrigin = mapEvalOrigin(cache, origin);
mapped.getEvalOrigin = () => mappedOrigin;
return mapped;
}
// If we get here then we were unable to change the source position
return frame;
}
class SourceMapSupport {
// Holding this keeps the current test file's registry reachable after its
// environment is gone, which is what lets a stray timer's stack still map.
// The next `install` drops it, so it is one file's worth of path strings at a
// time rather than an accumulating set.
activeCache = null;
nullCache = null;
// Keyed weakly, so a registry nothing else holds takes its cache with it.
cachesByRegistry = new WeakMap();
suppressWarnings = false;
reportedMapPaths = new Set();
// V8 calls the formatter unbound, and `install` compares it by identity, so
// the bound copy has to be the same object every time.
boundFormatStackTrace;
constructor() {
this.boundFormatStackTrace = this.formatStackTrace.bind(this);
}
/**
* Replaces `Error.prepareStackTrace` in the current realm, so `error.stack`
* renders frames against the original sources.
*
* Stays installed for the lifetime of the worker — each call swaps in its
* own cache. There is deliberately no `uninstall`: restoring V8's formatter
* at teardown would leave an error thrown after the environment is torn
* down pointing into the transformed file. Holding the cache does not
* retain the environment: it only ever references path strings and parsed
* source maps.
*/
install(sourceMaps, options = {}) {
this.suppressWarnings = options.suppressWarnings === true;
const cache = this.cacheFor(sourceMaps);
// The registry going out of service is the last chance to remember where
// each file's map lives — a stray timer from the file it served can still
// throw, and a file no formatted stack mentioned was never recorded.
if (this.activeCache != null && this.activeCache !== cache) {
this.activeCache.rememberAll();
}
this.activeCache = cache;
if (Error.prepareStackTrace !== this.boundFormatStackTrace) {
Error.prepareStackTrace = this.boundFormatStackTrace;
}
}
/** One remapped `CallSite`, `level` frames above the caller. */
getCallsite(level, sourceMaps) {
const levelAfterThisCall = level + 1;
const stack = (0, _callsites().default)()[levelAfterThisCall];
const sourceMap = this.cacheFor(sourceMaps).get(stack.getFileName() ?? '');
if (sourceMap != null) {
addSourceMapConsumer(stack, sourceMap);
}
return stack;
}
// One cache per registry, so repeated lookups in a test file reuse it.
cacheFor(sourceMaps) {
if (sourceMaps == null) {
this.nullCache ??= new _SourceMapCache.SourceMapCache(null, _nodeFileReader.nodeFileReader, (mapPath, generatedPath) => this.reportUnparsable(mapPath, generatedPath));
return this.nullCache;
}
let cache = this.cachesByRegistry.get(sourceMaps);
if (cache == null) {
cache = new _SourceMapCache.SourceMapCache(sourceMaps, _nodeFileReader.nodeFileReader, (mapPath, generatedPath) => this.reportUnparsable(mapPath, generatedPath));
this.cachesByRegistry.set(sourceMaps, cache);
}
return cache;
}
// A broken map silently leaves frames at their generated positions, which
// reads as "source maps do not work". Say so, once per map.
reportUnparsable(mapPath, generatedPath) {
if (this.suppressWarnings || this.reportedMapPaths.has(mapPath)) {
return;
}
this.reportedMapPaths.add(mapPath);
const location = mapPath === generatedPath ? 'the inline source map' : `the source map at ${mapPath}`;
console.warn(`Failed to parse ${location} for ${generatedPath}; its stack frames stay untranslated.`);
}
formatStackTrace(error, stack) {
const name = isPresent(error.name) ? error.name : 'Error';
const message = error.message ?? '';
const errorString = `${name}: ${message}`;
const cache = this.activeCache;
if (cache == null) {
return errorString + stack.map(frame => `\n at ${frameToString(frame)}`).join('');
}
return errorString + stack.map(frame => `\n at ${frameToString(wrapCallSite(cache, frame))}`).join('');
}
}
exports.SourceMapSupport = SourceMapSupport;
/***/ },
/***/ "./src/getCallsite.ts"
(__unused_webpack_module, exports, __webpack_require__) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports["default"] = getCallsite;
function _callsites() {
const data = _interopRequireDefault(require("callsites"));
_callsites = function () {
return data;
};
return data;
}
var _SourceMapCache = __webpack_require__("./src/SourceMapCache.ts");
var _SourceMapSupport = __webpack_require__("./src/SourceMapSupport.ts");
var _nodeFileReader = __webpack_require__("./src/nodeFileReader.ts");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// TODO: replace with `util.getCallSites()`, whose `columnNumber` landed in
// Node 22.14 — the floor is still 18.
const reportNothing = () => {
// Broken maps stay quiet on this path: the formatter reports them when a
// stack is formatted, which is when anyone can act on it.
};
// One cache per registry, so repeated lookups in a test file reuse it. Parsed
// maps are shared with the formatter through the process-lifetime parse cache.
const cachesByRegistry = new WeakMap();
let nullCache = null;
function cacheFor(sourceMaps) {
if (sourceMaps == null) {
nullCache ??= new _SourceMapCache.SourceMapCache(null, _nodeFileReader.nodeFileReader, reportNothing);
return nullCache;
}
let cache = cachesByRegistry.get(sourceMaps);
if (cache == null) {
cache = new _SourceMapCache.SourceMapCache(sourceMaps, _nodeFileReader.nodeFileReader, reportNothing);
cachesByRegistry.set(sourceMaps, cache);
}
return cache;
}
/**
* One remapped `CallSite`, `level` frames above the caller.
*
* @deprecated Use `SourceMapSupport#getCallsite` instead.
*/
function getCallsite(level, sourceMaps) {
const levelAfterThisCall = level + 1;
const stack = (0, _callsites().default)()[levelAfterThisCall];
const sourceMap = cacheFor(sourceMaps).get(stack.getFileName() ?? '');
if (sourceMap != null) {
(0, _SourceMapSupport.addSourceMapConsumer)(stack, sourceMap);
}
return stack;
}
/***/ },
/***/ "./src/nodeFileReader.ts"
(__unused_webpack_module, exports) {
Object.defineProperty(exports, "__esModule", ({
value: true
}));
exports.nodeFileReader = void 0;
function _nodeUrl() {
const data = require("node:url");
_nodeUrl = function () {
return data;
};
return data;
}
function _convertSourceMap() {
const data = require("convert-source-map");
_convertSourceMap = function () {
return data;
};
return data;
}
function _gracefulFs() {
const data = require("graceful-fs");
_gracefulFs = function () {
return data;
};
return data;
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// A scheme needs at least two characters before the colon, so a Windows drive
// letter is not mistaken for one.
const ABSOLUTE_URI_REGEXP = /^[a-zA-Z][\w+\-.]+:/;
function toPath(url) {
if (!url.startsWith('file:')) {
return url;
}
try {
return (0, _nodeUrl().fileURLToPath)(url);
} catch {
return url;
}
}
const nodeFileReader = exports.nodeFileReader = {
read(urlOrPath) {
if (urlOrPath.startsWith('data:')) {
try {
// Reads both the base64 and the URI encoding the spec allows. Takes a
// whole comment rather than the URL on its own.
return (0, _convertSourceMap().fromComment)(`//# sourceMappingURL=${urlOrPath}`).toObject();
} catch {
return null;
}
}
// A resource named by any other scheme — `node:internal/…`,
// `webpack:///…` — cannot be read off disk.
if (ABSOLUTE_URI_REGEXP.test(urlOrPath) && !urlOrPath.startsWith('file:')) {
return null;
}
const filePath = toPath(urlOrPath);
try {
return (0, _gracefulFs().existsSync)(filePath) ? (0, _gracefulFs().readFileSync)(filePath, 'utf8') : null;
} catch {
return null;
}
},
toPath,
toUrl(pathOrUrl) {
return ABSOLUTE_URI_REGEXP.test(pathOrUrl) ? pathOrUrl : (0, _nodeUrl().pathToFileURL)(pathOrUrl).href;
}
};
/***/ }
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ const __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ const cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ const module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
let __webpack_exports__ = {};
// This entry needs to be wrapped in an IIFE because it uses a non-standard name for the exports (exports).
(() => {
let exports = __webpack_exports__;
Object.defineProperty(exports, "__esModule", ({
value: true
}));
Object.defineProperty(exports, "SourceMapSupport", ({
enumerable: true,
get: function () {
return _SourceMapSupport.SourceMapSupport;
}
}));
Object.defineProperty(exports, "getCallsite", ({
enumerable: true,
get: function () {
return _getCallsite.default;
}
}));
var _getCallsite = _interopRequireDefault(__webpack_require__("./src/getCallsite.ts"));
var _SourceMapSupport = __webpack_require__("./src/SourceMapSupport.ts");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
})();
module.exports = __webpack_exports__;
/******/ })()
;
+4
View File
@@ -0,0 +1,4 @@
import cjsModule from './index.js';
export const SourceMapSupport = cjsModule.SourceMapSupport;
export const getCallsite = cjsModule.getCallsite;
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@jest/source-map",
"version": "30.5.0",
"repository": {
"type": "git",
"url": "https://github.com/jestjs/jest.git",
"directory": "packages/jest-source-map"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"require": "./build/index.js",
"import": "./build/index.mjs",
"default": "./build/index.js"
},
"./package.json": "./package.json"
},
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"callsites": "^3.1.0",
"convert-source-map": "^2.0.0",
"graceful-fs": "^4.2.11"
},
"devDependencies": {
"@types/convert-source-map": "^2.0.3",
"@types/graceful-fs": "^4.1.9"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
"publishConfig": {
"access": "public"
},
"gitHead": "912baa37afaa979e8179240c238cb493f960527a"
}