UNPKG

vitest

Version:

Next generation testing framework powered by Vite

1,573 lines (1,507 loc) 152 kB
import fs, { existsSync, readFileSync, writeFileSync, promises } from 'node:fs'; import { getTests, generateHash, createTaskName, calculateSuiteHash, someTasksAreOnly, interpretTaskModes, getTestName, hasFailed, getSuites, getTasks, getFullName } from '@vitest/runner/utils'; import * as pathe from 'pathe'; import { relative, basename, resolve as resolve$1, join, normalize, dirname } from 'pathe'; import c from 'tinyrainbow'; import { t as truncateString, e as errorBanner, F as F_POINTER, d as divider, f as formatTimeString, a as taskFail, s as separator, b as F_CHECK, c as F_DOWN_RIGHT, g as formatProjectName, h as getStateSymbol, w as withLabel, r as renderSnapshotSummary, p as padSummaryTitle, i as getStateString$1, j as formatTime, k as countTestErrors, l as F_TREE_NODE_END, m as F_TREE_NODE_MIDDLE, n as noun, o as F_RIGHT } from './utils.BS4fH3nR.js'; import { stripVTControlCharacters } from 'node:util'; import { isPrimitive, toArray, deepMerge, notNullish } from '@vitest/utils/helpers'; import { readdir, stat, readFile, mkdir, writeFile } from 'node:fs/promises'; import { performance as performance$1 } from 'node:perf_hooks'; import { defaultStackIgnorePatterns, parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map'; import { i as isTTY } from './env.D4Lgay0q.js'; import { Console } from 'node:console'; import { Writable } from 'node:stream'; import { inspect } from '@vitest/utils/display'; import nodeos__default, { hostname } from 'node:os'; import { x } from 'tinyexec'; import { distDir } from '../path.js'; import { parseAstAsync } from 'vite'; import { positionToOffset, lineSplitRE } from '@vitest/utils/offset'; import { createRequire } from 'node:module'; function groupBy(collection, iteratee) { return collection.reduce((acc, item) => { const key = iteratee(item); acc[key] ||= []; acc[key].push(item); return acc; }, {}); } function stdout() { // @ts-expect-error Node.js maps process.stdout to console._stdout // eslint-disable-next-line no-console return console._stdout || process.stdout; } function escapeRegExp(s) { // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function wildcardPatternToRegExp(pattern) { const negated = pattern[0] === "!"; if (negated) pattern = pattern.slice(1); let regexp = `${pattern.split("*").map(escapeRegExp).join(".*")}$`; if (negated) regexp = `(?!${regexp})`; return new RegExp(`^${regexp}`, "i"); } function createIndexLocationsMap(source) { const map = /* @__PURE__ */ new Map(); let index = 0; let line = 1; let column = 1; for (const char of source) { map.set(index++, { line, column }); if (char === "\n" || char === "\r\n") { line++; column = 0; } else column++; } return map; } function createLocationsIndexMap(source) { const map = /* @__PURE__ */ new Map(); let index = 0; let line = 1; let column = 1; for (const char of source) { map.set(`${line}:${column}`, index++); if (char === "\n" || char === "\r\n") { line++; column = 0; } else column++; } return map; } function hasFailedSnapshot(suite) { return getTests(suite).some((s) => { return s.result?.errors?.some((e) => typeof e?.message === "string" && e.message.match(/Snapshot .* mismatched/)); }); } function convertTasksToEvents(file, onTask) { const packs = []; const events = []; function visit(suite) { onTask?.(suite); packs.push([ suite.id, suite.result, suite.meta ]); events.push([ suite.id, "suite-prepare", void 0 ]); suite.tasks.forEach((task) => { if (task.type === "suite") visit(task); else { onTask?.(task); if (suite.mode !== "skip" && suite.mode !== "todo") { packs.push([ task.id, task.result, task.meta ]); events.push([ task.id, "test-prepare", void 0 ]); task.annotations.forEach((annotation) => { events.push([ task.id, "test-annotation", { annotation } ]); }); task.artifacts.forEach((artifact) => { events.push([ task.id, "test-artifact", { artifact } ]); }); events.push([ task.id, "test-finished", void 0 ]); } } }); events.push([ suite.id, "suite-finished", void 0 ]); } visit(file); return { packs, events }; } // src/vlq.ts var comma = ",".charCodeAt(0); var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var intToChar = new Uint8Array(64); var charToInt = new Uint8Array(128); for (let i = 0; i < chars.length; i++) { const c = chars.charCodeAt(i); intToChar[i] = c; charToInt[c] = i; } function decodeInteger(reader, relative) { let value = 0; let shift = 0; let integer = 0; do { const c = reader.next(); integer = charToInt[c]; value |= (integer & 31) << shift; shift += 5; } while (integer & 32); const shouldNegate = value & 1; value >>>= 1; if (shouldNegate) { value = -2147483648 | -value; } return relative + value; } function hasMoreVlq(reader, max) { if (reader.pos >= max) return false; return reader.peek() !== comma; } var StringReader = class { constructor(buffer) { this.pos = 0; this.buffer = buffer; } next() { return this.buffer.charCodeAt(this.pos++); } peek() { return this.buffer.charCodeAt(this.pos); } indexOf(char) { const { buffer, pos } = this; const idx = buffer.indexOf(char, pos); return idx === -1 ? buffer.length : idx; } }; // src/sourcemap-codec.ts function decode(mappings) { const { length } = mappings; const reader = new StringReader(mappings); const decoded = []; let genColumn = 0; let sourcesIndex = 0; let sourceLine = 0; let sourceColumn = 0; let namesIndex = 0; do { const semi = reader.indexOf(";"); const line = []; let sorted = true; let lastCol = 0; genColumn = 0; while (reader.pos < semi) { let seg; genColumn = decodeInteger(reader, genColumn); if (genColumn < lastCol) sorted = false; lastCol = genColumn; if (hasMoreVlq(reader, semi)) { sourcesIndex = decodeInteger(reader, sourcesIndex); sourceLine = decodeInteger(reader, sourceLine); sourceColumn = decodeInteger(reader, sourceColumn); if (hasMoreVlq(reader, semi)) { namesIndex = decodeInteger(reader, namesIndex); seg = [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex]; } else { seg = [genColumn, sourcesIndex, sourceLine, sourceColumn]; } } else { seg = [genColumn]; } line.push(seg); reader.pos++; } if (!sorted) sort(line); decoded.push(line); reader.pos = semi + 1; } while (reader.pos <= length); return decoded; } function sort(line) { line.sort(sortComparator$1); } function sortComparator$1(a, b) { return a[0] - b[0]; } // Matches the scheme of a URL, eg "http://" const schemeRegex = /^[\w+.-]+:\/\//; /** * Matches the parts of a URL: * 1. Scheme, including ":", guaranteed. * 2. User/password, including "@", optional. * 3. Host, guaranteed. * 4. Port, including ":", optional. * 5. Path, including "/", optional. * 6. Query, including "?", optional. * 7. Hash, including "#", optional. */ const urlRegex = /^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/; /** * File URLs are weird. They dont' need the regular `//` in the scheme, they may or may not start * with a leading `/`, they can have a domain (but only if they don't start with a Windows drive). * * 1. Host, optional. * 2. Path, which may include "/", guaranteed. * 3. Query, including "?", optional. * 4. Hash, including "#", optional. */ const fileRegex = /^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i; function isAbsoluteUrl(input) { return schemeRegex.test(input); } function isSchemeRelativeUrl(input) { return input.startsWith('//'); } function isAbsolutePath(input) { return input.startsWith('/'); } function isFileUrl(input) { return input.startsWith('file:'); } function isRelative(input) { return /^[.?#]/.test(input); } function parseAbsoluteUrl(input) { const match = urlRegex.exec(input); return makeUrl(match[1], match[2] || '', match[3], match[4] || '', match[5] || '/', match[6] || '', match[7] || ''); } function parseFileUrl(input) { const match = fileRegex.exec(input); const path = match[2]; return makeUrl('file:', '', match[1] || '', '', isAbsolutePath(path) ? path : '/' + path, match[3] || '', match[4] || ''); } function makeUrl(scheme, user, host, port, path, query, hash) { return { scheme, user, host, port, path, query, hash, type: 7 /* Absolute */, }; } function parseUrl(input) { if (isSchemeRelativeUrl(input)) { const url = parseAbsoluteUrl('http:' + input); url.scheme = ''; url.type = 6 /* SchemeRelative */; return url; } if (isAbsolutePath(input)) { const url = parseAbsoluteUrl('http://foo.com' + input); url.scheme = ''; url.host = ''; url.type = 5 /* AbsolutePath */; return url; } if (isFileUrl(input)) return parseFileUrl(input); if (isAbsoluteUrl(input)) return parseAbsoluteUrl(input); const url = parseAbsoluteUrl('http://foo.com/' + input); url.scheme = ''; url.host = ''; url.type = input ? input.startsWith('?') ? 3 /* Query */ : input.startsWith('#') ? 2 /* Hash */ : 4 /* RelativePath */ : 1 /* Empty */; return url; } function stripPathFilename(path) { // If a path ends with a parent directory "..", then it's a relative path with excess parent // paths. It's not a file, so we can't strip it. if (path.endsWith('/..')) return path; const index = path.lastIndexOf('/'); return path.slice(0, index + 1); } function mergePaths(url, base) { normalizePath(base, base.type); // If the path is just a "/", then it was an empty path to begin with (remember, we're a relative // path). if (url.path === '/') { url.path = base.path; } else { // Resolution happens relative to the base path's directory, not the file. url.path = stripPathFilename(base.path) + url.path; } } /** * The path can have empty directories "//", unneeded parents "foo/..", or current directory * "foo/.". We need to normalize to a standard representation. */ function normalizePath(url, type) { const rel = type <= 4 /* RelativePath */; const pieces = url.path.split('/'); // We need to preserve the first piece always, so that we output a leading slash. The item at // pieces[0] is an empty string. let pointer = 1; // Positive is the number of real directories we've output, used for popping a parent directory. // Eg, "foo/bar/.." will have a positive 2, and we can decrement to be left with just "foo". let positive = 0; // We need to keep a trailing slash if we encounter an empty directory (eg, splitting "foo/" will // generate `["foo", ""]` pieces). And, if we pop a parent directory. But once we encounter a // real directory, we won't need to append, unless the other conditions happen again. let addTrailingSlash = false; for (let i = 1; i < pieces.length; i++) { const piece = pieces[i]; // An empty directory, could be a trailing slash, or just a double "//" in the path. if (!piece) { addTrailingSlash = true; continue; } // If we encounter a real directory, then we don't need to append anymore. addTrailingSlash = false; // A current directory, which we can always drop. if (piece === '.') continue; // A parent directory, we need to see if there are any real directories we can pop. Else, we // have an excess of parents, and we'll need to keep the "..". if (piece === '..') { if (positive) { addTrailingSlash = true; positive--; pointer--; } else if (rel) { // If we're in a relativePath, then we need to keep the excess parents. Else, in an absolute // URL, protocol relative URL, or an absolute path, we don't need to keep excess. pieces[pointer++] = piece; } continue; } // We've encountered a real directory. Move it to the next insertion pointer, which accounts for // any popped or dropped directories. pieces[pointer++] = piece; positive++; } let path = ''; for (let i = 1; i < pointer; i++) { path += '/' + pieces[i]; } if (!path || (addTrailingSlash && !path.endsWith('/..'))) { path += '/'; } url.path = path; } /** * Attempts to resolve `input` URL/path relative to `base`. */ function resolve(input, base) { if (!input && !base) return ''; const url = parseUrl(input); let inputType = url.type; if (base && inputType !== 7 /* Absolute */) { const baseUrl = parseUrl(base); const baseType = baseUrl.type; switch (inputType) { case 1 /* Empty */: url.hash = baseUrl.hash; // fall through case 2 /* Hash */: url.query = baseUrl.query; // fall through case 3 /* Query */: case 4 /* RelativePath */: mergePaths(url, baseUrl); // fall through case 5 /* AbsolutePath */: // The host, user, and port are joined, you can't copy one without the others. url.user = baseUrl.user; url.host = baseUrl.host; url.port = baseUrl.port; // fall through case 6 /* SchemeRelative */: // The input doesn't have a schema at least, so we need to copy at least that over. url.scheme = baseUrl.scheme; } if (baseType > inputType) inputType = baseType; } normalizePath(url, inputType); const queryHash = url.query + url.hash; switch (inputType) { // This is impossible, because of the empty checks at the start of the function. // case UrlType.Empty: case 2 /* Hash */: case 3 /* Query */: return queryHash; case 4 /* RelativePath */: { // The first char is always a "/", and we need it to be relative. const path = url.path.slice(1); if (!path) return queryHash || '.'; if (isRelative(base || input) && !isRelative(path)) { // If base started with a leading ".", or there is no base and input started with a ".", // then we need to ensure that the relative path starts with a ".". We don't know if // relative starts with a "..", though, so check before prepending. return './' + path + queryHash; } return path + queryHash; } case 5 /* AbsolutePath */: return url.path + queryHash; default: return url.scheme + '//' + url.user + url.host + url.port + url.path + queryHash; } } // src/trace-mapping.ts // src/strip-filename.ts function stripFilename(path) { if (!path) return ""; const index = path.lastIndexOf("/"); return path.slice(0, index + 1); } // src/resolve.ts function resolver$1(mapUrl, sourceRoot) { const from = stripFilename(mapUrl); const prefix = sourceRoot ? sourceRoot + "/" : ""; return (source) => resolve(prefix + (source || ""), from); } // src/sourcemap-segment.ts var COLUMN = 0; var SOURCES_INDEX = 1; var SOURCE_LINE = 2; var SOURCE_COLUMN = 3; var NAMES_INDEX = 4; var REV_GENERATED_LINE = 1; var REV_GENERATED_COLUMN = 2; // src/sort.ts function maybeSort(mappings, owned) { const unsortedIndex = nextUnsortedSegmentLine(mappings, 0); if (unsortedIndex === mappings.length) return mappings; if (!owned) mappings = mappings.slice(); for (let i = unsortedIndex; i < mappings.length; i = nextUnsortedSegmentLine(mappings, i + 1)) { mappings[i] = sortSegments(mappings[i], owned); } return mappings; } function nextUnsortedSegmentLine(mappings, start) { for (let i = start; i < mappings.length; i++) { if (!isSorted(mappings[i])) return i; } return mappings.length; } function isSorted(line) { for (let j = 1; j < line.length; j++) { if (line[j][COLUMN] < line[j - 1][COLUMN]) { return false; } } return true; } function sortSegments(line, owned) { if (!owned) line = line.slice(); return line.sort(sortComparator); } function sortComparator(a, b) { return a[COLUMN] - b[COLUMN]; } // src/by-source.ts function buildBySources(decoded, memos) { const sources = memos.map(() => []); for (let i = 0; i < decoded.length; i++) { const line = decoded[i]; for (let j = 0; j < line.length; j++) { const seg = line[j]; if (seg.length === 1) continue; const sourceIndex2 = seg[SOURCES_INDEX]; const sourceLine = seg[SOURCE_LINE]; const sourceColumn = seg[SOURCE_COLUMN]; const source = sources[sourceIndex2]; const segs = source[sourceLine] || (source[sourceLine] = []); segs.push([sourceColumn, i, seg[COLUMN]]); } } for (let i = 0; i < sources.length; i++) { const source = sources[i]; for (let j = 0; j < source.length; j++) { const line = source[j]; if (line) line.sort(sortComparator); } } return sources; } // src/binary-search.ts var found = false; function binarySearch(haystack, needle, low, high) { while (low <= high) { const mid = low + (high - low >> 1); const cmp = haystack[mid][COLUMN] - needle; if (cmp === 0) { found = true; return mid; } if (cmp < 0) { low = mid + 1; } else { high = mid - 1; } } found = false; return low - 1; } function upperBound(haystack, needle, index) { for (let i = index + 1; i < haystack.length; index = i++) { if (haystack[i][COLUMN] !== needle) break; } return index; } function lowerBound(haystack, needle, index) { for (let i = index - 1; i >= 0; index = i--) { if (haystack[i][COLUMN] !== needle) break; } return index; } function memoizedState() { return { lastKey: -1, lastNeedle: -1, lastIndex: -1 }; } function memoizedBinarySearch(haystack, needle, state, key) { const { lastKey, lastNeedle, lastIndex } = state; let low = 0; let high = haystack.length - 1; if (key === lastKey) { if (needle === lastNeedle) { found = lastIndex !== -1 && haystack[lastIndex][COLUMN] === needle; return lastIndex; } if (needle >= lastNeedle) { low = lastIndex === -1 ? 0 : lastIndex; } else { high = lastIndex; } } state.lastKey = key; state.lastNeedle = needle; return state.lastIndex = binarySearch(haystack, needle, low, high); } // src/types.ts function parse$1(map) { return typeof map === "string" ? JSON.parse(map) : map; } // src/trace-mapping.ts var LINE_GTR_ZERO = "`line` must be greater than 0 (lines start at line 1)"; var COL_GTR_EQ_ZERO = "`column` must be greater than or equal to 0 (columns start at column 0)"; var LEAST_UPPER_BOUND = -1; var GREATEST_LOWER_BOUND = 1; var TraceMap = class { constructor(map, mapUrl) { const isString = typeof map === "string"; if (!isString && map._decodedMemo) return map; const parsed = parse$1(map); const { version, file, names, sourceRoot, sources, sourcesContent } = parsed; this.version = version; this.file = file; this.names = names || []; this.sourceRoot = sourceRoot; this.sources = sources; this.sourcesContent = sourcesContent; this.ignoreList = parsed.ignoreList || parsed.x_google_ignoreList || void 0; const resolve = resolver$1(mapUrl, sourceRoot); this.resolvedSources = sources.map(resolve); const { mappings } = parsed; if (typeof mappings === "string") { this._encoded = mappings; this._decoded = void 0; } else if (Array.isArray(mappings)) { this._encoded = void 0; this._decoded = maybeSort(mappings, isString); } else if (parsed.sections) { throw new Error(`TraceMap passed sectioned source map, please use FlattenMap export instead`); } else { throw new Error(`invalid source map: ${JSON.stringify(parsed)}`); } this._decodedMemo = memoizedState(); this._bySources = void 0; this._bySourceMemos = void 0; } }; function cast(map) { return map; } function decodedMappings(map) { var _a; return (_a = cast(map))._decoded || (_a._decoded = decode(cast(map)._encoded)); } function originalPositionFor(map, needle) { let { line, column, bias } = needle; line--; if (line < 0) throw new Error(LINE_GTR_ZERO); if (column < 0) throw new Error(COL_GTR_EQ_ZERO); const decoded = decodedMappings(map); if (line >= decoded.length) return OMapping(null, null, null, null); const segments = decoded[line]; const index = traceSegmentInternal( segments, cast(map)._decodedMemo, line, column, bias || GREATEST_LOWER_BOUND ); if (index === -1) return OMapping(null, null, null, null); const segment = segments[index]; if (segment.length === 1) return OMapping(null, null, null, null); const { names, resolvedSources } = map; return OMapping( resolvedSources[segment[SOURCES_INDEX]], segment[SOURCE_LINE] + 1, segment[SOURCE_COLUMN], segment.length === 5 ? names[segment[NAMES_INDEX]] : null ); } function generatedPositionFor(map, needle) { const { source, line, column, bias } = needle; return generatedPosition(map, source, line, column, bias || GREATEST_LOWER_BOUND, false); } function eachMapping(map, cb) { const decoded = decodedMappings(map); const { names, resolvedSources } = map; for (let i = 0; i < decoded.length; i++) { const line = decoded[i]; for (let j = 0; j < line.length; j++) { const seg = line[j]; const generatedLine = i + 1; const generatedColumn = seg[0]; let source = null; let originalLine = null; let originalColumn = null; let name = null; if (seg.length !== 1) { source = resolvedSources[seg[1]]; originalLine = seg[2] + 1; originalColumn = seg[3]; } if (seg.length === 5) name = names[seg[4]]; cb({ generatedLine, generatedColumn, source, originalLine, originalColumn, name }); } } } function OMapping(source, line, column, name) { return { source, line, column, name }; } function GMapping(line, column) { return { line, column }; } function traceSegmentInternal(segments, memo, line, column, bias) { let index = memoizedBinarySearch(segments, column, memo, line); if (found) { index = (bias === LEAST_UPPER_BOUND ? upperBound : lowerBound)(segments, column, index); } else if (bias === LEAST_UPPER_BOUND) index++; if (index === -1 || index === segments.length) return -1; return index; } function generatedPosition(map, source, line, column, bias, all) { var _a, _b; line--; if (line < 0) throw new Error(LINE_GTR_ZERO); if (column < 0) throw new Error(COL_GTR_EQ_ZERO); const { sources, resolvedSources } = map; let sourceIndex2 = sources.indexOf(source); if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source); if (sourceIndex2 === -1) return all ? [] : GMapping(null, null); const bySourceMemos = (_a = cast(map))._bySourceMemos || (_a._bySourceMemos = sources.map(memoizedState)); const generated = (_b = cast(map))._bySources || (_b._bySources = buildBySources(decodedMappings(map), bySourceMemos)); const segments = generated[sourceIndex2][line]; if (segments == null) return all ? [] : GMapping(null, null); const memo = bySourceMemos[sourceIndex2]; const index = traceSegmentInternal(segments, memo, line, column, bias); if (index === -1) return GMapping(null, null); const segment = segments[index]; return GMapping(segment[REV_GENERATED_LINE] + 1, segment[REV_GENERATED_COLUMN]); } // AST walker module for ESTree compatible trees // An ancestor walk keeps an array of ancestor nodes (including the // current node) and passes them to the callback as third parameter // (and also as state parameter when no other state is present). function ancestor(node, visitors, baseVisitor, state, override) { var ancestors = []; if (!baseVisitor) { baseVisitor = base ; }(function c(node, st, override) { var type = override || node.type; var isNew = node !== ancestors[ancestors.length - 1]; if (isNew) { ancestors.push(node); } visitNode(baseVisitor, type, node, st, c); if (visitors[type]) { visitors[type](node, st || ancestors, ancestors); } if (isNew) { ancestors.pop(); } })(node, state, override); } function skipThrough(node, st, c) { c(node, st); } function ignore$1(_node, _st, _c) {} function visitNode(baseVisitor, type, node, st, c) { if (baseVisitor[type] == null) { throw new Error(("No walker function defined for node type " + type)) } baseVisitor[type](node, st, c); } // Node walkers. var base = {}; base.Program = base.BlockStatement = base.StaticBlock = function (node, st, c) { for (var i = 0, list = node.body; i < list.length; i += 1) { var stmt = list[i]; c(stmt, st, "Statement"); } }; base.Statement = skipThrough; base.EmptyStatement = ignore$1; base.ExpressionStatement = base.ParenthesizedExpression = base.ChainExpression = function (node, st, c) { return c(node.expression, st, "Expression"); }; base.IfStatement = function (node, st, c) { c(node.test, st, "Expression"); c(node.consequent, st, "Statement"); if (node.alternate) { c(node.alternate, st, "Statement"); } }; base.LabeledStatement = function (node, st, c) { return c(node.body, st, "Statement"); }; base.BreakStatement = base.ContinueStatement = ignore$1; base.WithStatement = function (node, st, c) { c(node.object, st, "Expression"); c(node.body, st, "Statement"); }; base.SwitchStatement = function (node, st, c) { c(node.discriminant, st, "Expression"); for (var i = 0, list = node.cases; i < list.length; i += 1) { var cs = list[i]; c(cs, st); } }; base.SwitchCase = function (node, st, c) { if (node.test) { c(node.test, st, "Expression"); } for (var i = 0, list = node.consequent; i < list.length; i += 1) { var cons = list[i]; c(cons, st, "Statement"); } }; base.ReturnStatement = base.YieldExpression = base.AwaitExpression = function (node, st, c) { if (node.argument) { c(node.argument, st, "Expression"); } }; base.ThrowStatement = base.SpreadElement = function (node, st, c) { return c(node.argument, st, "Expression"); }; base.TryStatement = function (node, st, c) { c(node.block, st, "Statement"); if (node.handler) { c(node.handler, st); } if (node.finalizer) { c(node.finalizer, st, "Statement"); } }; base.CatchClause = function (node, st, c) { if (node.param) { c(node.param, st, "Pattern"); } c(node.body, st, "Statement"); }; base.WhileStatement = base.DoWhileStatement = function (node, st, c) { c(node.test, st, "Expression"); c(node.body, st, "Statement"); }; base.ForStatement = function (node, st, c) { if (node.init) { c(node.init, st, "ForInit"); } if (node.test) { c(node.test, st, "Expression"); } if (node.update) { c(node.update, st, "Expression"); } c(node.body, st, "Statement"); }; base.ForInStatement = base.ForOfStatement = function (node, st, c) { c(node.left, st, "ForInit"); c(node.right, st, "Expression"); c(node.body, st, "Statement"); }; base.ForInit = function (node, st, c) { if (node.type === "VariableDeclaration") { c(node, st); } else { c(node, st, "Expression"); } }; base.DebuggerStatement = ignore$1; base.FunctionDeclaration = function (node, st, c) { return c(node, st, "Function"); }; base.VariableDeclaration = function (node, st, c) { for (var i = 0, list = node.declarations; i < list.length; i += 1) { var decl = list[i]; c(decl, st); } }; base.VariableDeclarator = function (node, st, c) { c(node.id, st, "Pattern"); if (node.init) { c(node.init, st, "Expression"); } }; base.Function = function (node, st, c) { if (node.id) { c(node.id, st, "Pattern"); } for (var i = 0, list = node.params; i < list.length; i += 1) { var param = list[i]; c(param, st, "Pattern"); } c(node.body, st, node.expression ? "Expression" : "Statement"); }; base.Pattern = function (node, st, c) { if (node.type === "Identifier") { c(node, st, "VariablePattern"); } else if (node.type === "MemberExpression") { c(node, st, "MemberPattern"); } else { c(node, st); } }; base.VariablePattern = ignore$1; base.MemberPattern = skipThrough; base.RestElement = function (node, st, c) { return c(node.argument, st, "Pattern"); }; base.ArrayPattern = function (node, st, c) { for (var i = 0, list = node.elements; i < list.length; i += 1) { var elt = list[i]; if (elt) { c(elt, st, "Pattern"); } } }; base.ObjectPattern = function (node, st, c) { for (var i = 0, list = node.properties; i < list.length; i += 1) { var prop = list[i]; if (prop.type === "Property") { if (prop.computed) { c(prop.key, st, "Expression"); } c(prop.value, st, "Pattern"); } else if (prop.type === "RestElement") { c(prop.argument, st, "Pattern"); } } }; base.Expression = skipThrough; base.ThisExpression = base.Super = base.MetaProperty = ignore$1; base.ArrayExpression = function (node, st, c) { for (var i = 0, list = node.elements; i < list.length; i += 1) { var elt = list[i]; if (elt) { c(elt, st, "Expression"); } } }; base.ObjectExpression = function (node, st, c) { for (var i = 0, list = node.properties; i < list.length; i += 1) { var prop = list[i]; c(prop, st); } }; base.FunctionExpression = base.ArrowFunctionExpression = base.FunctionDeclaration; base.SequenceExpression = function (node, st, c) { for (var i = 0, list = node.expressions; i < list.length; i += 1) { var expr = list[i]; c(expr, st, "Expression"); } }; base.TemplateLiteral = function (node, st, c) { for (var i = 0, list = node.quasis; i < list.length; i += 1) { var quasi = list[i]; c(quasi, st); } for (var i$1 = 0, list$1 = node.expressions; i$1 < list$1.length; i$1 += 1) { var expr = list$1[i$1]; c(expr, st, "Expression"); } }; base.TemplateElement = ignore$1; base.UnaryExpression = base.UpdateExpression = function (node, st, c) { c(node.argument, st, "Expression"); }; base.BinaryExpression = base.LogicalExpression = function (node, st, c) { c(node.left, st, "Expression"); c(node.right, st, "Expression"); }; base.AssignmentExpression = base.AssignmentPattern = function (node, st, c) { c(node.left, st, "Pattern"); c(node.right, st, "Expression"); }; base.ConditionalExpression = function (node, st, c) { c(node.test, st, "Expression"); c(node.consequent, st, "Expression"); c(node.alternate, st, "Expression"); }; base.NewExpression = base.CallExpression = function (node, st, c) { c(node.callee, st, "Expression"); if (node.arguments) { for (var i = 0, list = node.arguments; i < list.length; i += 1) { var arg = list[i]; c(arg, st, "Expression"); } } }; base.MemberExpression = function (node, st, c) { c(node.object, st, "Expression"); if (node.computed) { c(node.property, st, "Expression"); } }; base.ExportNamedDeclaration = base.ExportDefaultDeclaration = function (node, st, c) { if (node.declaration) { c(node.declaration, st, node.type === "ExportNamedDeclaration" || node.declaration.id ? "Statement" : "Expression"); } if (node.source) { c(node.source, st, "Expression"); } if (node.attributes) { for (var i = 0, list = node.attributes; i < list.length; i += 1) { var attr = list[i]; c(attr, st); } } }; base.ExportAllDeclaration = function (node, st, c) { if (node.exported) { c(node.exported, st); } c(node.source, st, "Expression"); if (node.attributes) { for (var i = 0, list = node.attributes; i < list.length; i += 1) { var attr = list[i]; c(attr, st); } } }; base.ImportAttribute = function (node, st, c) { c(node.value, st, "Expression"); }; base.ImportDeclaration = function (node, st, c) { for (var i = 0, list = node.specifiers; i < list.length; i += 1) { var spec = list[i]; c(spec, st); } c(node.source, st, "Expression"); if (node.attributes) { for (var i$1 = 0, list$1 = node.attributes; i$1 < list$1.length; i$1 += 1) { var attr = list$1[i$1]; c(attr, st); } } }; base.ImportExpression = function (node, st, c) { c(node.source, st, "Expression"); if (node.options) { c(node.options, st, "Expression"); } }; base.ImportSpecifier = base.ImportDefaultSpecifier = base.ImportNamespaceSpecifier = base.Identifier = base.PrivateIdentifier = base.Literal = ignore$1; base.TaggedTemplateExpression = function (node, st, c) { c(node.tag, st, "Expression"); c(node.quasi, st, "Expression"); }; base.ClassDeclaration = base.ClassExpression = function (node, st, c) { return c(node, st, "Class"); }; base.Class = function (node, st, c) { if (node.id) { c(node.id, st, "Pattern"); } if (node.superClass) { c(node.superClass, st, "Expression"); } c(node.body, st); }; base.ClassBody = function (node, st, c) { for (var i = 0, list = node.body; i < list.length; i += 1) { var elt = list[i]; c(elt, st); } }; base.MethodDefinition = base.PropertyDefinition = base.Property = function (node, st, c) { if (node.computed) { c(node.key, st, "Expression"); } if (node.value) { c(node.value, st, "Expression"); } }; /// <reference types="../types/index.d.ts" /> // (c) 2020-present Andrea Giammarchi const {parse: $parse, stringify: $stringify} = JSON; const {keys} = Object; const Primitive = String; // it could be Number const primitive = 'string'; // it could be 'number' const ignore = {}; const object = 'object'; const noop = (_, value) => value; const primitives = value => ( value instanceof Primitive ? Primitive(value) : value ); const Primitives = (_, value) => ( typeof value === primitive ? new Primitive(value) : value ); const resolver = (input, lazy, parsed, $) => output => { for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) { const k = ke[y]; const value = output[k]; if (value instanceof Primitive) { const tmp = input[+value]; if (typeof tmp === object && !parsed.has(tmp)) { parsed.add(tmp); output[k] = ignore; lazy.push({ o: output, k, r: tmp }); } else output[k] = $.call(output, k, tmp); } else if (output[k] !== ignore) output[k] = $.call(output, k, value); } return output; }; const set = (known, input, value) => { const index = Primitive(input.push(value) - 1); known.set(value, index); return index; }; /** * Converts a specialized flatted string into a JS value. * @param {string} text * @param {(this: any, key: string, value: any) => any} [reviver] * @returns {any} */ const parse = (text, reviver) => { const input = $parse(text, Primitives).map(primitives); const $ = reviver || noop; let value = input[0]; if (typeof value === object && value) { const lazy = []; const revive = resolver(input, lazy, new Set, $); value = revive(value); let i = 0; while (i < lazy.length) { // it could be a lazy.shift() but that's costly const {o, k, r} = lazy[i++]; o[k] = $.call(o, k, revive(r)); } } return $.call({'': value}, '', value); }; /** * Converts a JS value into a specialized flatted string. * @param {any} value * @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer] * @param {string | number | undefined} [space] * @returns {string} */ const stringify = (value, replacer, space) => { const $ = replacer && typeof replacer === object ? (k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) : (replacer || noop); const known = new Map; const input = []; const output = []; let i = +set(known, input, $.call({'': value}, '', value)); let firstRun = !i; while (i < input.length) { firstRun = true; output[i] = $stringify(input[i++], replace, space); } return '[' + output.join(',') + ']'; function replace(key, value) { if (firstRun) { firstRun = !firstRun; return value; } const after = $.call(this, key, value); switch (typeof after) { case object: if (after === null) return after; case primitive: return known.get(after) || set(known, input, after); } return after; } }; async function collectTests(ctx, filepath) { const request = await ctx.vite.environments.ssr.transformRequest(filepath); if (!request) return null; const ast = await parseAstAsync(request.code); const testFilepath = relative(ctx.config.root, filepath); const projectName = ctx.name; const file = { filepath, type: "suite", id: generateHash(`${testFilepath}${projectName ? `${projectName}:__typecheck__` : "__typecheck__"}`), name: testFilepath, fullName: testFilepath, mode: "run", tasks: [], start: ast.start, end: ast.end, projectName, meta: { typecheck: true }, file: null }; file.file = file; const definitions = []; const getName = (callee) => { if (!callee) return null; if (callee.type === "Identifier") return callee.name; if (callee.type === "CallExpression") return getName(callee.callee); if (callee.type === "TaggedTemplateExpression") return getName(callee.tag); if (callee.type === "MemberExpression") { if (callee.object?.type === "Identifier" && [ "it", "test", "describe", "suite" ].includes(callee.object.name)) return callee.object?.name; // direct call as `__vite_ssr_exports_0__.test()` if (callee.object?.name?.startsWith("__vite_ssr_")) return getName(callee.property); // call as `__vite_ssr__.test.skip()` return getName(callee.object?.property); } // unwrap (0, ...) if (callee.type === "SequenceExpression" && callee.expressions.length === 2) { const [e0, e1] = callee.expressions; if (e0.type === "Literal" && e0.value === 0) return getName(e1); } return null; }; ancestor(ast, { CallExpression(node) { const { callee } = node; const name = getName(callee); if (!name) return; if (![ "it", "test", "describe", "suite" ].includes(name)) return; const property = callee?.property?.name; let mode = !property || property === name ? "run" : property; // they will be picked up in the next iteration if ([ "each", "for", "skipIf", "runIf" ].includes(mode)) return; let start; const end = node.end; // .each if (callee.type === "CallExpression") start = callee.end; else if (callee.type === "TaggedTemplateExpression") start = callee.end + 1; else start = node.start; const { arguments: [messageNode] } = node; const message = messageNode?.type === "Literal" || messageNode?.type === "TemplateLiteral" ? request.code.slice(messageNode.start + 1, messageNode.end - 1) : request.code.slice(messageNode.start, messageNode.end); // cannot statically analyze, so we always skip it if (mode === "skipIf" || mode === "runIf") mode = "skip"; definitions.push({ start, end, name: message, type: name === "it" || name === "test" ? "test" : "suite", mode, task: null }); } }); let lastSuite = file; const updateLatestSuite = (index) => { while (lastSuite.suite && lastSuite.end < index) lastSuite = lastSuite.suite; return lastSuite; }; definitions.sort((a, b) => a.start - b.start).forEach((definition) => { const latestSuite = updateLatestSuite(definition.start); let mode = definition.mode; if (latestSuite.mode !== "run") // inherit suite mode, if it's set mode = latestSuite.mode; if (definition.type === "suite") { const task = { type: definition.type, id: "", suite: latestSuite, file, tasks: [], mode, name: definition.name, fullName: createTaskName([lastSuite.fullName, definition.name]), fullTestName: createTaskName([lastSuite.fullTestName, definition.name]), end: definition.end, start: definition.start, meta: { typecheck: true } }; definition.task = task; latestSuite.tasks.push(task); lastSuite = task; return; } const task = { type: definition.type, id: "", suite: latestSuite, file, mode, timeout: 0, context: {}, name: definition.name, fullName: createTaskName([lastSuite.fullName, definition.name]), fullTestName: createTaskName([lastSuite.fullTestName, definition.name]), end: definition.end, start: definition.start, annotations: [], artifacts: [], meta: { typecheck: true } }; definition.task = task; latestSuite.tasks.push(task); }); calculateSuiteHash(file); const hasOnly = someTasksAreOnly(file); interpretTaskModes(file, ctx.config.testNamePattern, void 0, void 0, void 0, hasOnly, false, ctx.config.allowOnly); return { file, parsed: request.code, filepath, map: request.map, definitions }; } const newLineRegExp = /\r?\n/; const errCodeRegExp = /error TS(?<errCode>\d+)/; async function makeTscErrorInfo(errInfo) { const [errFilePathPos = "", ...errMsgRawArr] = errInfo.split(":"); if (!errFilePathPos || errMsgRawArr.length === 0 || errMsgRawArr.join("").length === 0) return ["unknown filepath", null]; const errMsgRaw = errMsgRawArr.join("").trim(); // get filePath, line, col const [errFilePath, errPos] = errFilePathPos.slice(0, -1).split("("); if (!errFilePath || !errPos) return ["unknown filepath", null]; const [errLine, errCol] = errPos.split(","); if (!errLine || !errCol) return [errFilePath, null]; // get errCode, errMsg const execArr = errCodeRegExp.exec(errMsgRaw); if (!execArr) return [errFilePath, null]; const errCodeStr = execArr.groups?.errCode ?? ""; if (!errCodeStr) return [errFilePath, null]; const line = Number(errLine); const col = Number(errCol); const errCode = Number(errCodeStr); return [errFilePath, { filePath: errFilePath, errCode, line, column: col, errMsg: errMsgRaw.slice(`error TS${errCode} `.length) }]; } async function getRawErrsMapFromTsCompile(tscErrorStdout) { const rawErrsMap = /* @__PURE__ */ new Map(); (await Promise.all(tscErrorStdout.split(newLineRegExp).reduce((prev, next) => { if (!next) return prev; else if (next[0] !== " ") prev.push(next); else prev[prev.length - 1] += `\n${next}`; return prev; }, []).map((errInfoLine) => makeTscErrorInfo(errInfoLine)))).forEach(([errFilePath, errInfo]) => { if (!errInfo) return; if (!rawErrsMap.has(errFilePath)) rawErrsMap.set(errFilePath, [errInfo]); else rawErrsMap.get(errFilePath)?.push(errInfo); }); return rawErrsMap; } class TypeCheckError extends Error { name = "TypeCheckError"; constructor(message, stacks) { super(message); this.message = message; this.stacks = stacks; } } class Typechecker { _onParseStart; _onParseEnd; _onWatcherRerun; _result = { files: [], sourceErrors: [], time: 0 }; _startTime = 0; _output = ""; _tests = {}; process; files = []; constructor(project) { this.project = project; } setFiles(files) { this.files = files; } onParseStart(fn) { this._onParseStart = fn; } onParseEnd(fn) { this._onParseEnd = fn; } onWatcherRerun(fn) { this._onWatcherRerun = fn; } async collectFileTests(filepath) { return collectTests(this.project, filepath); } getFiles() { return this.files; } async collectTests() { const tests = (await Promise.all(this.getFiles().map((filepath) => this.collectFileTests(filepath)))).reduce((acc, data) => { if (!data) return acc; acc[data.filepath] = data; return acc; }, {}); this._tests = tests; return tests; } markPassed(file) { if (!file.result?.state) file.result = { state: "pass" }; const markTasks = (tasks) => { for (const task of tasks) { if ("tasks" in task) markTasks(task.tasks); if (!task.result?.state && (task.mode === "run" || task.mode === "queued")) task.result = { state: "pass" }; } }; markTasks(file.tasks); } async prepareResults(output) { // Detect if tsc output is help text instead of error output // This happens when tsconfig.json is missing and tsc can't find any config if (output.includes("The TypeScript Compiler - Version") || output.includes("COMMON COMMANDS")) { const { typecheck } = this.project.config; const msg = `TypeScript compiler returned help text instead of type checking results. This usually means the tsconfig file was not found. Possible solutions: 1. Ensure '${typecheck.tsconfig || "tsconfig.json"}' exists in your project root\n 2. If using a custom tsconfig, verify the path in your Vitest config:\n test: { typecheck: { tsconfig: 'path/to/tsconfig.json' } }\n 3. Check that the tsconfig file is valid JSON`; throw new Error(msg); } const typeErrors = await this.parseTscLikeOutput(output); const testFiles = new Set(this.getFiles()); if (!this._tests) this._tests = await this.collectTests(); const sourceErrors = []; const files = []; testFiles.forEach((path) => { const { file, definitions, map, parsed } = this._tests[path]; const errors = typeErrors.get(path); files.push(file); if (!errors) { this.markPassed(file); return; } const sortedDefinitions = [...definitions.sort((a, b) => b.start - a.start)]; // has no map for ".js" files that use // @ts-check const traceMap = map && new TraceMap(map); const indexMap = createLocationsIndexMap(parsed); const markState = (task, state) => { task.result = { state: task.mode === "run" || task.mode === "only" ? state : task.mode }; if (task.suite) markState(task.suite, state); else if (task.file && task !== task.file) markState(task.file, state); }; errors.forEach(({ error, originalError }) => { const processedPos = traceMap ? findGeneratedPosition(traceMap, { line: originalError.line, column: originalError.column, source: basename(path) }) : originalError; const line = processedPos.line ?? originalError.line; const column = processedPos.column ?? originalError.column; const index = indexMap.get(`${line}:${column}`); const definition = index != null && sortedDefinitions.find((def) => def.start <= index && def.end >= index); const suite = definition ? definition.task : file; const state = suite.mode === "run" || suite.mode === "only" ? "fail" : suite.mode; const errors = suite.result?.errors || []; suite.result = { state, errors }; errors.push(error); if (state === "fail") { if (suite.suite) markState(suite.suite, "fail"); else if (suite.file && suite !== suite.file) markState(suite.file, "fail"); } }); this.markPassed(file); }); typeErrors.forEach((errors, path) => { if (!testFiles.has(path)) sourceErrors.push(...errors.map(({ error }) => error)); }); return { files, sourceErrors, time: performance$1.now() - this._startTime }; } async parseTscLikeOutput(output) { const errorsMap = await getRawErrsMapFromTsCompile(output); const typesErrors = /* @__PURE__ */ new Map(); errorsMap.forEach((errors, path) => { const filepath = resolve$1(this.project.config.root, path); const suiteErrors = errors.map((info) => { const limit = Error.stackTraceLimit; Error.stackTraceLimit = 0; // Some expect-type errors have the most useful information on the second line e.g. `This expression is not callable.\n Type 'ExpectString<number>' has no call signatures.` const errMsg = info.errMsg.replace(/\r?\n\s*(Type .* has no call signatures)/g, " $1"); const error = new TypeCheckError(errMsg, [{ file: filepath, line: info.line, column: info.column, method: "" }]); Error.stackTraceLimit = limit; return { originalError: info, error: { name: error.name, message: errMsg, stacks: error.stacks, stack: "" } }; }); typesErrors.set(filepath, suiteErrors); }); return typesErrors; } async stop() { this.process?.kill(); this.process = void 0; } async ensurePackageInstalled(ctx, checker) { if (checker !== "tsc" && checker !== "vue-tsc") return; const packageName = checker === "tsc" ? "typescript" : "vue-tsc"; await ctx.packageInstaller.ensureInstalled(packageName, ctx.config.root); } getExitCode() { return this.process?.exitCode != null && this.process.exitCode; } getOutput() { return this._output; } async spawn() { const { root, watch, typecheck } = this.project.config; const args = [ "--noEmit", "--pretty", "false", "--incremental", "--tsBuildInfoFile", join(process.versions.pnp ? join(nodeos__default.tmpdir(), this.project.hash) : distDir, "tsconfig.tmp.tsbuildinfo") ]; // use builtin watcher because it's faster if (watch) args.push("--watch"); if (typecheck.allowJs) args.push("--allowJs", "--checkJs"); if (typecheck.tsconfig) args.push("-p", resolve$1(root, typecheck.tsconfig)); this._output = ""; this._startTime = performance$1.now(); const child = x(typecheck.checker, args, { nodeOptions: { cwd: root, stdio: "pipe" }, throwOnError: false }); this.process = child.process; let rerunTriggered = false; let dataReceived = false; return new Promise(