initial commit of actions

This commit is contained in:
2026-01-31 18:56:04 +01:00
commit 949ece5785
44660 changed files with 12034344 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env node
import run from '../lib/cli.js';
run();

View File

@@ -0,0 +1,109 @@
'use strict';
const path = require('node:path');
const url = require('node:url');
const v8 = require('node:v8');
const {Worker} = require('node:worker_threads');
const {
classify,
hasExtension,
isHelperish,
matches,
normalizeFileForMatching,
normalizePatterns,
} = require('../lib/glob-helpers.cjs');
const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs.
let data;
let sync;
let worker;
const resolveGlobsSync = (projectDir, overrideExtensions, overrideFiles) => {
if (worker === undefined) {
const dataBuffer = new SharedArrayBuffer(MAX_DATA_LENGTH_EXCLUSIVE);
data = new Uint8Array(dataBuffer);
const syncBuffer = new SharedArrayBuffer(4);
sync = new Int32Array(syncBuffer);
const filename = path.join(__dirname, '../lib/eslint-plugin-helper-worker.js');
worker = new Worker(url.pathToFileURL(filename), {
workerData: {
dataBuffer,
syncBuffer,
firstMessage: {projectDir, overrideExtensions, overrideFiles},
},
});
worker.unref();
} else {
worker.postMessage({projectDir, overrideExtensions, overrideFiles});
}
const synchronize = Atomics.wait(sync, 0, 0, 10_000);
if (synchronize === 'timed-out') {
throw new Error('Timed out resolving AVA configuration');
}
const byteLength = Atomics.exchange(sync, 0, 0);
if (byteLength === MAX_DATA_LENGTH_EXCLUSIVE) {
throw new Error('Globs are over 100 KiB and cannot be resolved');
}
const globsOrError = v8.deserialize(data.slice(0, byteLength));
if (globsOrError instanceof Error) {
throw globsOrError;
}
return globsOrError;
};
const helperCache = new Map();
function load(projectDir, overrides) {
const cacheKey = `${JSON.stringify(overrides)}\n${projectDir}`;
if (helperCache.has(cacheKey)) {
return helperCache.get(cacheKey);
}
let helperPatterns = [];
if (overrides && overrides.helpers !== undefined) {
if (!Array.isArray(overrides.helpers) || overrides.helpers.length === 0) {
throw new Error('The helpers override must be an array containing glob patterns.');
}
helperPatterns = normalizePatterns(overrides.helpers);
}
const globs = resolveGlobsSync(projectDir, overrides && overrides.extensions, overrides && overrides.files);
const classifyForESLint = file => {
const {isTest} = classify(file, globs);
let isHelper = false;
if (!isTest && hasExtension(globs.extensions, file)) {
file = normalizeFileForMatching(projectDir, file);
isHelper = isHelperish(file) || (helperPatterns.length > 0 && matches(file, helperPatterns));
}
return {isHelper, isTest};
};
const helper = Object.freeze({
classifyFile: classifyForESLint,
classifyImport(importPath) {
if (hasExtension(globs.extensions, importPath)) {
// The importPath has one of the test file extensions: we can classify
// it directly.
return classifyForESLint(importPath);
}
// Add the first extension. If multiple extensions are available, assume
// patterns are not biased to any particular extension.
return classifyForESLint(`${importPath}.${globs.extensions[0]}`);
},
});
helperCache.set(cacheKey, helper);
return helper;
}
exports.load = load;

View File

@@ -0,0 +1,2 @@
'use strict';
module.exports = require('../lib/worker/main.cjs');

View File

@@ -0,0 +1,12 @@
import type {TestFn} from '../types/test-fn.cjs';
export * from '../types/assertions.cjs';
export * from '../types/try-fn.cjs';
export * from '../types/test-fn.cjs';
export * from '../types/subscribable.cjs';
/** Call to declare a test, or chain to declare hooks or test modifiers */
declare const test: TestFn;
/** Call to declare a test, or chain to declare hooks or test modifiers */
export default test;

View File

@@ -0,0 +1,12 @@
import type {TestFn} from '../types/test-fn.cjs';
export * from '../types/assertions.cjs';
export * from '../types/try-fn.cjs';
export * from '../types/test-fn.cjs';
export * from '../types/subscribable.cjs';
/** Call to declare a test, or chain to declare hooks or test modifiers */
declare const test: TestFn;
/** Call to declare a test, or chain to declare hooks or test modifiers */
export default test;

View File

@@ -0,0 +1 @@
export {default} from '../lib/worker/main.cjs';

View File

@@ -0,0 +1,2 @@
'use strict';
module.exports = require('../lib/worker/plugin.cjs');

View File

@@ -0,0 +1,6 @@
import type {SharedWorker} from '../types/shared-worker.cjs';
export function registerSharedWorker<Data = unknown>(options: SharedWorker.Plugin.RegistrationOptions<'ava-4', Data>): SharedWorker.Plugin.Protocol<Data>;
// Add overloads for additional protocols.
export type {SharedWorker} from '../types/shared-worker.cjs';

View File

@@ -0,0 +1,6 @@
import type {SharedWorker} from '../types/shared-worker.cjs';
export function registerSharedWorker<Data = unknown>(options: SharedWorker.Plugin.RegistrationOptions<'ava-4', Data>): SharedWorker.Plugin.Protocol<Data>;
// Add overloads for additional protocols.
export type {SharedWorker} from '../types/shared-worker.cjs';

View File

@@ -0,0 +1,4 @@
import * as plugin from '../lib/worker/plugin.cjs';
const {registerSharedWorker} = plugin;
export {registerSharedWorker};

4
github/codeql-action-v2/node_modules/ava/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,4 @@
// For compatibility with resolution algorithms other than Node16.
export * from './entrypoints/main.cjs';
export {default} from './entrypoints/main.cjs';

344
github/codeql-action-v2/node_modules/ava/lib/api.js generated vendored Normal file
View File

@@ -0,0 +1,344 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import process from 'node:process';
import arrify from 'arrify';
import chunkd from 'chunkd';
import commonPathPrefix from 'common-path-prefix';
import Emittery from 'emittery';
import ms from 'ms';
import pMap from 'p-map';
import resolveCwd from 'resolve-cwd';
import tempDir from 'temp-dir';
import fork from './fork.js';
import * as globs from './globs.js';
import isCi from './is-ci.js';
import {getApplicableLineNumbers} from './line-numbers.js';
import {setCappedTimeout} from './now-and-timers.cjs';
import {observeWorkerProcess} from './plugin-support/shared-workers.js';
import RunStatus from './run-status.js';
import scheduler from './scheduler.js';
import serializeError from './serialize-error.js';
function resolveModules(modules) {
return arrify(modules).map(name => {
const modulePath = resolveCwd.silent(name);
if (modulePath === undefined) {
throw new Error(`Could not resolve required module ${name}`);
}
return modulePath;
});
}
function getFilePathPrefix(files) {
if (files.length === 1) {
// Get the correct prefix up to the basename.
return commonPathPrefix([files[0], path.dirname(files[0])]);
}
return commonPathPrefix(files);
}
class TimeoutTrigger {
constructor(fn, waitMs = 0) {
this.fn = fn.bind(null);
this.ignoreUntil = 0;
this.waitMs = waitMs;
this.timer = undefined;
}
debounce() {
if (this.timer === undefined) {
this.timer = setCappedTimeout(() => this.trigger(), this.waitMs);
} else {
this.timer.refresh();
}
}
discard() {
// N.B. this.timer is not cleared so if debounce() is called after it will
// not run again.
clearTimeout(this.timer);
}
ignoreFor(periodMs) {
this.ignoreUntil = Math.max(this.ignoreUntil, Date.now() + periodMs);
}
trigger() {
if (Date.now() >= this.ignoreUntil) {
this.fn();
}
}
}
export default class Api extends Emittery {
constructor(options) {
super();
this.options = {match: [], moduleTypes: {}, ...options};
this.options.require = resolveModules(this.options.require);
this._cacheDir = null;
this._interruptHandler = () => {};
if (options.ranFromCli) {
process.on('SIGINT', () => this._interruptHandler());
}
}
async run({files: selectedFiles = [], filter = [], runtimeOptions = {}} = {}) { // eslint-disable-line complexity
let setupOrGlobError;
const apiOptions = this.options;
// Each run will have its own status. It can only be created when test files
// have been found.
let runStatus;
// Irrespectively, perform some setup now, before finding test files.
// Track active forks and manage timeouts.
const failFast = apiOptions.failFast === true;
let bailed = false;
const pendingWorkers = new Set();
const timedOutWorkerFiles = new Set();
let timeoutTrigger;
if (apiOptions.timeout && !apiOptions.debug) {
const timeout = ms(apiOptions.timeout);
timeoutTrigger = new TimeoutTrigger(() => {
// If failFast is active, prevent new test files from running after
// the current ones are exited.
if (failFast) {
bailed = true;
}
runStatus.emitStateChange({type: 'timeout', period: timeout});
for (const worker of pendingWorkers) {
timedOutWorkerFiles.add(worker.file);
worker.exit();
}
}, timeout);
} else {
timeoutTrigger = new TimeoutTrigger(() => {});
}
this._interruptHandler = () => {
if (bailed) {
// Exiting already
return;
}
// Prevent new test files from running
bailed = true;
// Make sure we don't run the timeout handler
timeoutTrigger.discard();
runStatus.emitStateChange({type: 'interrupt'});
for (const worker of pendingWorkers) {
worker.exit();
}
};
const {providers = []} = this.options;
let testFiles;
try {
testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
if (selectedFiles.length === 0) {
selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
cwd: this.options.projectDir,
filter: filter.map(({pattern}) => pattern),
providers,
testFiles,
});
}
} catch (error) {
selectedFiles = [];
setupOrGlobError = error;
}
const selectionInsights = {
filter,
ignoredFilterPatternFiles: selectedFiles.ignoredFilterPatternFiles || [],
testFileCount: testFiles.length,
selectionCount: selectedFiles.length,
};
try {
if (this.options.parallelRuns) {
const {currentIndex, totalRuns} = this.options.parallelRuns;
const fileCount = selectedFiles.length;
// The files must be in the same order across all runs, so sort them.
const defaultComparator = (a, b) => a.localeCompare(b, [], {numeric: true});
selectedFiles = selectedFiles.sort(this.options.sortTestFiles || defaultComparator);
selectedFiles = chunkd(selectedFiles, currentIndex, totalRuns);
const currentFileCount = selectedFiles.length;
runStatus = new RunStatus(fileCount, {currentFileCount, currentIndex, totalRuns}, selectionInsights);
} else {
// If a custom sorter was configured, use it.
if (this.options.sortTestFiles) {
selectedFiles = selectedFiles.sort(this.options.sortTestFiles);
}
runStatus = new RunStatus(selectedFiles.length, null, selectionInsights);
}
selectedFiles = scheduler.failingTestsFirst(selectedFiles, this._getLocalCacheDir(), this.options.cacheEnabled);
const debugWithoutSpecificFile = Boolean(this.options.debug) && !this.options.debug.active && selectedFiles.length !== 1;
await this.emit('run', {
bailWithoutReporting: debugWithoutSpecificFile,
clearLogOnNextRun: runtimeOptions.clearLogOnNextRun === true,
debug: Boolean(this.options.debug),
failFastEnabled: failFast,
filePathPrefix: getFilePathPrefix(selectedFiles),
files: selectedFiles,
matching: apiOptions.match.length > 0,
previousFailures: runtimeOptions.previousFailures || 0,
runOnlyExclusive: runtimeOptions.runOnlyExclusive === true,
runVector: runtimeOptions.runVector || 0,
status: runStatus,
});
if (setupOrGlobError) {
throw setupOrGlobError;
}
// Bail out early if no files were found, or when debugging and there is not a single specific test file to debug.
if (selectedFiles.length === 0 || debugWithoutSpecificFile) {
return runStatus;
}
runStatus.on('stateChange', record => {
if (record.testFile && !timedOutWorkerFiles.has(record.testFile)) {
// Debounce the timer whenever there is activity from workers that
// haven't already timed out.
timeoutTrigger.debounce();
}
if (failFast && (record.type === 'hook-failed' || record.type === 'test-failed' || record.type === 'worker-failed')) {
// Prevent new test files from running once a test has failed.
bailed = true;
// Try to stop currently scheduled tests.
for (const worker of pendingWorkers) {
worker.notifyOfPeerFailure();
}
}
});
const providerStates = [];
await Promise.all(providers.map(async ({type, main}) => {
const state = await main.compile({cacheDir: this._createCacheDir(), files: testFiles});
if (state !== null) {
providerStates.push({type, state});
}
}));
// Resolve the correct concurrency value. Note that `os.cpus()` can return empty arrays on
// platforms not officially supported by Node.js. Use 1 as a minimum.
// See <https://github.com/nodejs/node/issues/38190>.
let concurrency = Math.max(1, os.cpus().length);
if (apiOptions.serial) {
concurrency = 1;
} else if (apiOptions.concurrency > 0) {
concurrency = apiOptions.concurrency;
} else if (isCi) {
concurrency = 2;
}
const deregisteredSharedWorkers = [];
// Try and run each file, limited by `concurrency`.
await pMap(selectedFiles, async file => {
// No new files should be run once a test has timed out or failed,
// and failFast is enabled.
if (bailed) {
return;
}
const lineNumbers = getApplicableLineNumbers(globs.normalizeFileForMatching(apiOptions.projectDir, file), filter);
// Removing `providers` and `sortTestFiles` fields because they cannot be transferred to the worker threads.
const {providers, sortTestFiles, ...forkOptions} = apiOptions;
const options = {
...forkOptions,
providerStates,
lineNumbers,
recordNewSnapshots: !isCi,
// If we're looking for matches, run every single test process in exclusive-only mode
runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true,
};
if (runtimeOptions.updateSnapshots) {
// Don't use in Object.assign() since it'll override options.updateSnapshots even when false.
options.updateSnapshots = true;
}
const worker = fork(file, options, apiOptions.nodeArguments);
worker.onStateChange(data => {
if (data.type === 'test-timeout-configured' && !apiOptions.debug) {
timeoutTrigger.ignoreFor(data.period);
}
});
runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
deregisteredSharedWorkers.push(observeWorkerProcess(worker, runStatus));
pendingWorkers.add(worker);
worker.promise.then(() => {
pendingWorkers.delete(worker);
});
timeoutTrigger.debounce();
await worker.promise;
}, {concurrency, stopOnError: false});
// Allow shared workers to clean up before the run ends.
await Promise.all(deregisteredSharedWorkers);
scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir());
} catch (error) {
if (error && error.name === 'AggregateError') {
for (const error_ of error.errors) {
runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error_)});
}
} else {
runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error)});
}
}
timeoutTrigger.discard();
return runStatus;
}
_getLocalCacheDir() {
return path.join(this.options.projectDir, 'node_modules', '.cache', 'ava');
}
_createCacheDir() {
if (this._cacheDir) {
return this._cacheDir;
}
const cacheDir = this.options.cacheEnabled === false
? fs.mkdtempSync(`${tempDir}${path.sep}`)
: this._getLocalCacheDir();
// Ensure cacheDir exists
fs.mkdirSync(cacheDir, {recursive: true});
this._cacheDir = cacheDir;
return cacheDir;
}
}

962
github/codeql-action-v2/node_modules/ava/lib/assert.js generated vendored Normal file
View File

@@ -0,0 +1,962 @@
import concordance from 'concordance';
import isError from 'is-error';
import isPromise from 'is-promise';
import concordanceOptions from './concordance-options.js';
import {CIRCULAR_SELECTOR, isLikeSelector, selectComparable} from './like-selector.js';
import {SnapshotError, VersionMismatchError} from './snapshot-manager.js';
function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) {
options = {...options, ...concordanceOptions};
const {diffGutters} = options.theme;
const {insertLine, deleteLine} = options.theme.string.diff;
return {
label: `Difference (${diffGutters.actual}${deleteLine.open}actual${deleteLine.close}, ${diffGutters.expected}${insertLine.open}expected${insertLine.close}):`,
formatted: concordance.diffDescriptors(actualDescriptor, expectedDescriptor, options),
};
}
function formatDescriptorWithLabel(label, descriptor) {
return {
label,
formatted: concordance.formatDescriptor(descriptor, concordanceOptions),
};
}
function formatWithLabel(label, value) {
return formatDescriptorWithLabel(label, concordance.describe(value, concordanceOptions));
}
const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop);
const noop = () => {};
const notImplemented = () => {
throw new Error('not implemented');
};
export class AssertionError extends Error {
constructor(options) {
super(options.message || '');
this.name = 'AssertionError';
this.assertion = options.assertion;
this.fixedSource = options.fixedSource;
this.improperUsage = options.improperUsage || false;
this.actualStack = options.actualStack;
this.operator = options.operator;
this.values = options.values || [];
// Raw expected and actual objects are stored for custom reporters
// (such as wallaby.js), that manage worker processes directly and
// use the values for custom diff views
this.raw = options.raw;
this.savedError = options.savedError || getErrorWithLongStackTrace();
}
}
export function checkAssertionMessage(assertion, message) {
if (message === undefined || typeof message === 'string') {
return true;
}
return new AssertionError({
assertion,
improperUsage: true,
message: 'The assertion message must be a string',
values: [formatWithLabel('Called with:', message)],
});
}
function getErrorWithLongStackTrace() {
const limitBefore = Error.stackTraceLimit;
Error.stackTraceLimit = Number.POSITIVE_INFINITY;
const error = new Error(); // eslint-disable-line unicorn/error-message
Error.stackTraceLimit = limitBefore;
return error;
}
function validateExpectations(assertion, expectations, numberArgs) { // eslint-disable-line complexity
if (numberArgs === 1 || expectations === null || expectations === undefined) {
if (expectations === null) {
throw new AssertionError({
assertion,
message: `The second argument to \`t.${assertion}()\` must be an expectation object or \`undefined\``,
values: [formatWithLabel('Called with:', expectations)],
});
}
expectations = {};
} else if (
typeof expectations === 'function'
|| typeof expectations === 'string'
|| expectations instanceof RegExp
|| typeof expectations !== 'object'
|| Array.isArray(expectations)
|| Object.keys(expectations).length === 0
) {
throw new AssertionError({
assertion,
message: `The second argument to \`t.${assertion}()\` must be an expectation object, \`null\` or \`undefined\``,
values: [formatWithLabel('Called with:', expectations)],
});
} else {
if (hasOwnProperty(expectations, 'instanceOf') && typeof expectations.instanceOf !== 'function') {
throw new AssertionError({
assertion,
message: `The \`instanceOf\` property of the second argument to \`t.${assertion}()\` must be a function`,
values: [formatWithLabel('Called with:', expectations)],
});
}
if (
hasOwnProperty(expectations, 'message')
&& typeof expectations.message !== 'string'
&& !(expectations.message instanceof RegExp)
&& !(typeof expectations.message === 'function')
) {
throw new AssertionError({
assertion,
message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string, regular expression or a function`,
values: [formatWithLabel('Called with:', expectations)],
});
}
if (hasOwnProperty(expectations, 'name') && typeof expectations.name !== 'string') {
throw new AssertionError({
assertion,
message: `The \`name\` property of the second argument to \`t.${assertion}()\` must be a string`,
values: [formatWithLabel('Called with:', expectations)],
});
}
if (hasOwnProperty(expectations, 'code') && typeof expectations.code !== 'string' && typeof expectations.code !== 'number') {
throw new AssertionError({
assertion,
message: `The \`code\` property of the second argument to \`t.${assertion}()\` must be a string or number`,
values: [formatWithLabel('Called with:', expectations)],
});
}
for (const key of Object.keys(expectations)) {
switch (key) {
case 'instanceOf':
case 'is':
case 'message':
case 'name':
case 'code': {
continue;
}
default: {
throw new AssertionError({
assertion,
message: `The second argument to \`t.${assertion}()\` contains unexpected properties`,
values: [formatWithLabel('Called with:', expectations)],
});
}
}
}
}
return expectations;
}
// Note: this function *must* throw exceptions, since it can be used
// as part of a pending assertion for promises.
function assertExpectations({assertion, actual, expectations, message, prefix, savedError}) {
if (!isError(actual)) {
throw new AssertionError({
assertion,
message,
savedError,
values: [formatWithLabel(`${prefix} exception that is not an error:`, actual)],
});
}
const actualStack = actual.stack;
if (hasOwnProperty(expectations, 'is') && actual !== expectations.is) {
throw new AssertionError({
assertion,
message,
savedError,
actualStack,
values: [
formatWithLabel(`${prefix} unexpected exception:`, actual),
formatWithLabel('Expected to be strictly equal to:', expectations.is),
],
});
}
if (expectations.instanceOf && !(actual instanceof expectations.instanceOf)) {
throw new AssertionError({
assertion,
message,
savedError,
actualStack,
values: [
formatWithLabel(`${prefix} unexpected exception:`, actual),
formatWithLabel('Expected instance of:', expectations.instanceOf),
],
});
}
if (typeof expectations.name === 'string' && actual.name !== expectations.name) {
throw new AssertionError({
assertion,
message,
savedError,
actualStack,
values: [
formatWithLabel(`${prefix} unexpected exception:`, actual),
formatWithLabel('Expected name to equal:', expectations.name),
],
});
}
if (typeof expectations.message === 'string' && actual.message !== expectations.message) {
throw new AssertionError({
assertion,
message,
savedError,
actualStack,
values: [
formatWithLabel(`${prefix} unexpected exception:`, actual),
formatWithLabel('Expected message to equal:', expectations.message),
],
});
}
if (expectations.message instanceof RegExp && !expectations.message.test(actual.message)) {
throw new AssertionError({
assertion,
message,
savedError,
actualStack,
values: [
formatWithLabel(`${prefix} unexpected exception:`, actual),
formatWithLabel('Expected message to match:', expectations.message),
],
});
}
if (typeof expectations.message === 'function' && expectations.message(actual.message) === false) {
throw new AssertionError({
assertion,
message,
savedError,
actualStack,
values: [
formatWithLabel(`${prefix} unexpected exception:`, actual),
formatWithLabel('Expected message to return true:', expectations.message),
],
});
}
if (expectations.code !== undefined && actual.code !== expectations.code) {
throw new AssertionError({
assertion,
message,
savedError,
actualStack,
values: [
formatWithLabel(`${prefix} unexpected exception:`, actual),
formatWithLabel('Expected code to equal:', expectations.code),
],
});
}
}
export class Assertions {
constructor({
pass = notImplemented,
pending = notImplemented,
fail = notImplemented,
skip = notImplemented,
compareWithSnapshot = notImplemented,
experiments = {},
disableSnapshots = false,
} = {}) {
const withSkip = assertionFn => {
assertionFn.skip = skip;
return assertionFn;
};
const checkMessage = (assertion, message) => {
const result = checkAssertionMessage(assertion, message);
if (result === true) {
return true;
}
fail(result);
return false;
};
this.pass = withSkip(() => {
pass();
return true;
});
this.fail = withSkip(message => {
if (!checkMessage('fail', message)) {
return false;
}
fail(new AssertionError({
assertion: 'fail',
message: message || 'Test failed via `t.fail()`',
}));
return false;
});
this.is = withSkip((actual, expected, message) => {
if (!checkMessage('is', message)) {
return false;
}
if (Object.is(actual, expected)) {
pass();
return true;
}
const result = concordance.compare(actual, expected, concordanceOptions);
const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
if (result.pass) {
fail(new AssertionError({
assertion: 'is',
message,
raw: {actual, expected},
values: [formatDescriptorWithLabel('Values are deeply equal to each other, but they are not the same:', actualDescriptor)],
}));
} else {
fail(new AssertionError({
assertion: 'is',
message,
raw: {actual, expected},
values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
}));
}
return false;
});
this.not = withSkip((actual, expected, message) => {
if (!checkMessage('not', message)) {
return false;
}
if (Object.is(actual, expected)) {
fail(new AssertionError({
assertion: 'not',
message,
raw: {actual, expected},
values: [formatWithLabel('Value is the same as:', actual)],
}));
return false;
}
pass();
return true;
});
this.deepEqual = withSkip((actual, expected, message) => {
if (!checkMessage('deepEqual', message)) {
return false;
}
const result = concordance.compare(actual, expected, concordanceOptions);
if (result.pass) {
pass();
return true;
}
const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
const expectedDescriptor = result.expected || concordance.describe(expected, concordanceOptions);
fail(new AssertionError({
assertion: 'deepEqual',
message,
raw: {actual, expected},
values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
}));
return false;
});
this.notDeepEqual = withSkip((actual, expected, message) => {
if (!checkMessage('notDeepEqual', message)) {
return false;
}
const result = concordance.compare(actual, expected, concordanceOptions);
if (result.pass) {
const actualDescriptor = result.actual || concordance.describe(actual, concordanceOptions);
fail(new AssertionError({
assertion: 'notDeepEqual',
message,
raw: {actual, expected},
values: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)],
}));
return false;
}
pass();
return true;
});
this.like = withSkip((actual, selector, message) => {
if (!checkMessage('like', message)) {
return false;
}
if (!isLikeSelector(selector)) {
fail(new AssertionError({
assertion: 'like',
improperUsage: true,
message: '`t.like()` selector must be a non-empty object',
values: [formatWithLabel('Called with:', selector)],
}));
return false;
}
let comparable;
try {
comparable = selectComparable(actual, selector);
} catch (error) {
if (error === CIRCULAR_SELECTOR) {
fail(new AssertionError({
assertion: 'like',
improperUsage: true,
message: '`t.like()` selector must not contain circular references',
values: [formatWithLabel('Called with:', selector)],
}));
return false;
}
throw error;
}
const result = concordance.compare(comparable, selector, concordanceOptions);
if (result.pass) {
pass();
return true;
}
const actualDescriptor = result.actual || concordance.describe(comparable, concordanceOptions);
const expectedDescriptor = result.expected || concordance.describe(selector, concordanceOptions);
fail(new AssertionError({
assertion: 'like',
message,
values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)],
}));
return false;
});
this.throws = withSkip((...args) => {
// Since arrow functions do not support 'arguments', we are using rest
// operator, so we can determine the total number of arguments passed
// to the function.
let [fn, expectations, message] = args;
if (!checkMessage('throws', message)) {
return;
}
if (typeof fn !== 'function') {
fail(new AssertionError({
assertion: 'throws',
improperUsage: true,
message: '`t.throws()` must be called with a function',
values: [formatWithLabel('Called with:', fn)],
}));
return;
}
try {
expectations = validateExpectations('throws', expectations, args.length, experiments);
} catch (error) {
fail(error);
return;
}
let retval;
let actual = null;
try {
retval = fn();
if (isPromise(retval)) {
// Here isPromise() checks if something is "promise like". Cast to an actual promise.
Promise.resolve(retval).catch(noop);
fail(new AssertionError({
assertion: 'throws',
message,
values: [formatWithLabel('Function returned a promise. Use `t.throwsAsync()` instead:', retval)],
}));
return;
}
} catch (error) {
actual = error;
}
if (!actual) {
fail(new AssertionError({
assertion: 'throws',
message,
values: [formatWithLabel('Function returned:', retval)],
}));
return;
}
try {
assertExpectations({
assertion: 'throws',
actual,
expectations,
message,
prefix: 'Function threw',
});
pass();
return actual;
} catch (error) {
fail(error);
}
});
this.throwsAsync = withSkip(async (...args) => {
let [thrower, expectations, message] = args;
if (!checkMessage('throwsAsync', message)) {
return;
}
if (typeof thrower !== 'function' && !isPromise(thrower)) {
fail(new AssertionError({
assertion: 'throwsAsync',
improperUsage: true,
message: '`t.throwsAsync()` must be called with a function or promise',
values: [formatWithLabel('Called with:', thrower)],
}));
return;
}
try {
expectations = validateExpectations('throwsAsync', expectations, args.length, experiments);
} catch (error) {
fail(error);
return;
}
const handlePromise = async (promise, wasReturned) => {
// Create an error object to record the stack before it gets lost in the promise chain.
const savedError = getErrorWithLongStackTrace();
// Handle "promise like" objects by casting to a real Promise.
const intermediate = Promise.resolve(promise).then(value => {
throw new AssertionError({
assertion: 'throwsAsync',
message,
savedError,
values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} resolved with:`, value)],
});
}, error => {
assertExpectations({
assertion: 'throwsAsync',
actual: error,
expectations,
message,
prefix: `${wasReturned ? 'Returned promise' : 'Promise'} rejected with`,
savedError,
});
return error;
});
pending(intermediate);
try {
return await intermediate;
} catch {
// Don't reject the returned promise, even if the assertion fails.
}
};
if (isPromise(thrower)) {
return handlePromise(thrower, false);
}
let retval;
let actual = null;
try {
retval = thrower();
} catch (error) {
actual = error;
}
if (actual) {
fail(new AssertionError({
assertion: 'throwsAsync',
message,
actualStack: actual.stack,
values: [formatWithLabel('Function threw synchronously. Use `t.throws()` instead:', actual)],
}));
return;
}
if (isPromise(retval)) {
return handlePromise(retval, true);
}
fail(new AssertionError({
assertion: 'throwsAsync',
message,
values: [formatWithLabel('Function returned:', retval)],
}));
});
this.notThrows = withSkip((fn, message) => {
if (!checkMessage('notThrows', message)) {
return;
}
if (typeof fn !== 'function') {
fail(new AssertionError({
assertion: 'notThrows',
improperUsage: true,
message: '`t.notThrows()` must be called with a function',
values: [formatWithLabel('Called with:', fn)],
}));
return;
}
try {
fn();
} catch (error) {
fail(new AssertionError({
assertion: 'notThrows',
message,
actualStack: error.stack,
values: [formatWithLabel('Function threw:', error)],
}));
return;
}
pass();
});
this.notThrowsAsync = withSkip((nonThrower, message) => {
if (!checkMessage('notThrowsAsync', message)) {
return Promise.resolve();
}
if (typeof nonThrower !== 'function' && !isPromise(nonThrower)) {
fail(new AssertionError({
assertion: 'notThrowsAsync',
improperUsage: true,
message: '`t.notThrowsAsync()` must be called with a function or promise',
values: [formatWithLabel('Called with:', nonThrower)],
}));
return Promise.resolve();
}
const handlePromise = async (promise, wasReturned) => {
// Create an error object to record the stack before it gets lost in the promise chain.
const savedError = getErrorWithLongStackTrace();
// Handle "promise like" objects by casting to a real Promise.
const intermediate = Promise.resolve(promise).then(noop, error => {
throw new AssertionError({
assertion: 'notThrowsAsync',
message,
savedError,
values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, error)],
});
});
pending(intermediate);
try {
return await intermediate;
} catch {
// Don't reject the returned promise, even if the assertion fails.
}
};
if (isPromise(nonThrower)) {
return handlePromise(nonThrower, false);
}
let retval;
try {
retval = nonThrower();
} catch (error) {
fail(new AssertionError({
assertion: 'notThrowsAsync',
message,
actualStack: error.stack,
values: [formatWithLabel('Function threw:', error)],
}));
return Promise.resolve();
}
if (!isPromise(retval)) {
fail(new AssertionError({
assertion: 'notThrowsAsync',
message,
values: [formatWithLabel('Function did not return a promise. Use `t.notThrows()` instead:', retval)],
}));
return Promise.resolve();
}
return handlePromise(retval, true);
});
this.snapshot = withSkip((expected, message) => {
if (disableSnapshots) {
fail(new AssertionError({
assertion: 'snapshot',
message: '`t.snapshot()` can only be used in tests',
improperUsage: true,
}));
return false;
}
if (message && message.id !== undefined) {
fail(new AssertionError({
assertion: 'snapshot',
message: 'AVA 4 no longer supports snapshot IDs',
improperUsage: true,
values: [formatWithLabel('Called with id:', message.id)],
}));
return false;
}
if (!checkMessage('snapshot', message)) {
return false;
}
if (message === '') {
fail(new AssertionError({
assertion: 'snapshot',
improperUsage: true,
message: 'The snapshot assertion message must be a non-empty string',
values: [formatWithLabel('Called with:', message)],
}));
return false;
}
let result;
try {
result = compareWithSnapshot({expected, message});
} catch (error) {
if (!(error instanceof SnapshotError)) {
throw error;
}
const improperUsage = {name: error.name, snapPath: error.snapPath};
if (error instanceof VersionMismatchError) {
improperUsage.snapVersion = error.snapVersion;
improperUsage.expectedVersion = error.expectedVersion;
}
fail(new AssertionError({
assertion: 'snapshot',
message: message || 'Could not compare snapshot',
improperUsage,
}));
return false;
}
if (result.pass) {
pass();
return true;
}
if (result.actual) {
fail(new AssertionError({
assertion: 'snapshot',
message: message || 'Did not match snapshot',
values: [formatDescriptorDiff(result.actual, result.expected, {invert: true})],
}));
} else {
// This can only occur in CI environments.
fail(new AssertionError({
assertion: 'snapshot',
message: message || 'No snapshot available — new snapshots are not created in CI environments',
}));
}
return false;
});
this.truthy = withSkip((actual, message) => {
if (!checkMessage('truthy', message)) {
return false;
}
if (actual) {
pass();
return true;
}
fail(new AssertionError({
assertion: 'truthy',
message,
operator: '!!',
values: [formatWithLabel('Value is not truthy:', actual)],
}));
return false;
});
this.falsy = withSkip((actual, message) => {
if (!checkMessage('falsy', message)) {
return false;
}
if (actual) {
fail(new AssertionError({
assertion: 'falsy',
message,
operator: '!',
values: [formatWithLabel('Value is not falsy:', actual)],
}));
return false;
}
pass();
return true;
});
this.true = withSkip((actual, message) => {
if (!checkMessage('true', message)) {
return false;
}
if (actual === true) {
pass();
return true;
}
fail(new AssertionError({
assertion: 'true',
message,
values: [formatWithLabel('Value is not `true`:', actual)],
}));
return false;
});
this.false = withSkip((actual, message) => {
if (!checkMessage('false', message)) {
return false;
}
if (actual === false) {
pass();
return true;
}
fail(new AssertionError({
assertion: 'false',
message,
values: [formatWithLabel('Value is not `false`:', actual)],
}));
return false;
});
this.regex = withSkip((string, regex, message) => {
if (!checkMessage('regex', message)) {
return false;
}
if (typeof string !== 'string') {
fail(new AssertionError({
assertion: 'regex',
improperUsage: true,
message: '`t.regex()` must be called with a string',
values: [formatWithLabel('Called with:', string)],
}));
return false;
}
if (!(regex instanceof RegExp)) {
fail(new AssertionError({
assertion: 'regex',
improperUsage: true,
message: '`t.regex()` must be called with a regular expression',
values: [formatWithLabel('Called with:', regex)],
}));
return false;
}
if (!regex.test(string)) {
fail(new AssertionError({
assertion: 'regex',
message,
values: [
formatWithLabel('Value must match expression:', string),
formatWithLabel('Regular expression:', regex),
],
}));
return false;
}
pass();
return true;
});
this.notRegex = withSkip((string, regex, message) => {
if (!checkMessage('notRegex', message)) {
return false;
}
if (typeof string !== 'string') {
fail(new AssertionError({
assertion: 'notRegex',
improperUsage: true,
message: '`t.notRegex()` must be called with a string',
values: [formatWithLabel('Called with:', string)],
}));
return false;
}
if (!(regex instanceof RegExp)) {
fail(new AssertionError({
assertion: 'notRegex',
improperUsage: true,
message: '`t.notRegex()` must be called with a regular expression',
values: [formatWithLabel('Called with:', regex)],
}));
return false;
}
if (regex.test(string)) {
fail(new AssertionError({
assertion: 'notRegex',
message,
values: [
formatWithLabel('Value must not match expression:', string),
formatWithLabel('Regular expression:', regex),
],
}));
return false;
}
pass();
return true;
});
this.assert = withSkip((actual, message) => {
if (!checkMessage('assert', message)) {
return false;
}
if (!actual) {
fail(new AssertionError({
assertion: 'assert',
message,
operator: '!!',
values: [formatWithLabel('Value is not truthy:', actual)],
}));
return false;
}
pass();
return true;
});
}
}

15
github/codeql-action-v2/node_modules/ava/lib/chalk.js generated vendored Normal file
View File

@@ -0,0 +1,15 @@
import {Chalk} from 'chalk'; // eslint-disable-line unicorn/import-style
let chalk = new Chalk(); // eslint-disable-line import/no-mutable-exports
export {chalk};
let configured = false;
export function set(options) {
if (configured) {
throw new Error('Chalk has already been configured');
}
configured = true;
chalk = new Chalk(options);
}

511
github/codeql-action-v2/node_modules/ava/lib/cli.js generated vendored Normal file
View File

@@ -0,0 +1,511 @@
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import arrify from 'arrify';
import ciParallelVars from 'ci-parallel-vars';
import figures from 'figures';
import yargs from 'yargs';
import {hideBin} from 'yargs/helpers'; // eslint-disable-line n/file-extension-in-import
import Api from './api.js';
import {chalk} from './chalk.js';
import validateEnvironmentVariables from './environment-variables.js';
import normalizeExtensions from './extensions.js';
import {normalizeGlobs, normalizePattern} from './globs.js';
import {controlFlow} from './ipc-flow-control.cjs';
import isCi from './is-ci.js';
import {splitPatternAndLineNumbers} from './line-numbers.js';
import {loadConfig} from './load-config.js';
import normalizeModuleTypes from './module-types.js';
import normalizeNodeArguments from './node-arguments.js';
import pkg from './pkg.cjs';
import providerManager from './provider-manager.js';
import DefaultReporter from './reporters/default.js';
import TapReporter from './reporters/tap.js';
import Watcher from './watcher.js';
function exit(message) {
console.error(`\n ${chalk.red(figures.cross)} ${message}`);
process.exit(1); // eslint-disable-line unicorn/no-process-exit
}
const coerceLastValue = value => Array.isArray(value) ? value.pop() : value;
const FLAGS = {
concurrency: {
alias: 'c',
coerce: coerceLastValue,
description: 'Max number of test files running at the same time (default: CPU cores)',
type: 'number',
},
'fail-fast': {
coerce: coerceLastValue,
description: 'Stop after first test failure',
type: 'boolean',
},
match: {
alias: 'm',
description: 'Only run tests with matching title (can be repeated)',
type: 'string',
},
'no-worker-threads': {
coerce: coerceLastValue,
description: 'Don\'t use worker threads',
type: 'boolean',
},
'node-arguments': {
coerce: coerceLastValue,
description: 'Additional Node.js arguments for launching worker processes (specify as a single string)',
type: 'string',
},
serial: {
alias: 's',
coerce: coerceLastValue,
description: 'Run tests serially',
type: 'boolean',
},
tap: {
alias: 't',
coerce: coerceLastValue,
description: 'Generate TAP output',
type: 'boolean',
},
timeout: {
alias: 'T',
coerce: coerceLastValue,
description: 'Set global timeout (milliseconds or human-readable, e.g. 10s, 2m)',
type: 'string',
},
'update-snapshots': {
alias: 'u',
coerce: coerceLastValue,
description: 'Update snapshots',
type: 'boolean',
},
verbose: {
alias: 'v',
coerce: coerceLastValue,
description: 'Enable verbose output (default)',
type: 'boolean',
},
watch: {
alias: 'w',
coerce: coerceLastValue,
description: 'Re-run tests when files change',
type: 'boolean',
},
};
export default async function loadCli() { // eslint-disable-line complexity
let conf;
let confError;
try {
const {argv: {config: configFile}} = yargs(hideBin(process.argv)).help(false).version(false);
const loaded = await loadConfig({configFile});
if (loaded.unsupportedFiles.length > 0) {
console.log(chalk.magenta(
` ${figures.warning} AVA does not support JSON config, ignoring:\n\n ${loaded.unsupportedFiles.join('\n ')}`,
));
}
conf = loaded.config;
if (conf.configFile && path.basename(conf.configFile) !== path.relative(conf.projectDir, conf.configFile)) {
console.log(chalk.magenta(` ${figures.warning} Using configuration from ${conf.configFile}`));
}
} catch (error) {
confError = error;
}
// Enter debug mode if the main process is being inspected. This assumes the
// worker processes are automatically inspected, too. It is not necessary to
// run AVA with the debug command, though it's allowed.
let activeInspector = false;
try {
const {default: inspector} = await import('node:inspector');
activeInspector = inspector.url() !== undefined;
} catch {}
let debug = activeInspector
? {
active: true,
break: false,
files: [],
host: undefined,
port: undefined,
} : null;
let resetCache = false;
const {argv} = yargs(hideBin(process.argv))
.scriptName('ava')
.version(pkg.version)
.parserConfiguration({
'boolean-negation': true,
'camel-case-expansion': false,
'combine-arrays': false,
'dot-notation': false,
'duplicate-arguments-array': true,
'flatten-duplicate-arrays': true,
'negation-prefix': 'no-',
'parse-numbers': true,
'populate--': true,
'set-placeholder-key': false,
'short-option-groups': true,
'strip-aliased': true,
'unknown-options-as-args': false,
})
.usage('$0 [<pattern>...]')
.usage('$0 debug [<pattern>...]')
.usage('$0 reset-cache')
.options({
color: {
description: 'Force color output',
type: 'boolean',
},
config: {
description: 'Specific JavaScript file for AVA to read its config from, instead of using package.json or ava.config.* files',
},
})
.command('* [<pattern>...]', 'Run tests', yargs => yargs.options(FLAGS).positional('pattern', {
array: true,
describe: 'Select which test files to run. Leave empty if you want AVA to run all test files as per your configuration. Accepts glob patterns, directories that (recursively) contain test files, and file paths optionally suffixed with a colon and comma-separated numbers and/or ranges identifying the 1-based line(s) of specific tests to run',
type: 'string',
}), argv => {
if (activeInspector) {
debug.files = argv.pattern || [];
}
})
.command(
'debug [<pattern>...]',
'Activate Node.js inspector and run a single test file',
yargs => yargs.options(FLAGS).options({
break: {
description: 'Break before the test file is loaded',
type: 'boolean',
},
host: {
default: '127.0.0.1',
description: 'Address or hostname through which you can connect to the inspector',
type: 'string',
},
port: {
default: 9229,
description: 'Port on which you can connect to the inspector',
type: 'number',
},
}).positional('pattern', {
demand: true,
describe: 'Glob pattern to select a single test file to debug, optionally suffixed with a colon and comma-separated numbers and/or ranges identifying the 1-based line(s) of specific tests to run',
type: 'string',
}),
argv => {
debug = {
active: activeInspector,
break: argv.break === true,
files: argv.pattern,
host: argv.host,
port: argv.port,
};
})
.command(
'reset-cache',
'Delete any temporary files and state kept by AVA, then exit',
yargs => yargs,
() => {
resetCache = true;
})
.example('$0')
.example('$0 test.js')
.example('$0 test.js:4,7-9')
.help();
const combined = {...conf};
for (const flag of Object.keys(FLAGS)) {
if (flag === 'no-worker-threads' && Reflect.has(argv, 'worker-threads')) {
combined.workerThreads = argv['worker-threads'];
continue;
}
if (argv[flag] !== undefined) {
if (flag === 'fail-fast') {
combined.failFast = argv[flag];
} else if (flag === 'update-snapshots') {
combined.updateSnapshots = argv[flag];
} else if (flag !== 'node-arguments') {
combined[flag] = argv[flag];
}
}
}
const chalkOptions = {level: 0};
if (combined.color !== false) {
const {supportsColor: {level}} = await import('chalk'); // eslint-disable-line unicorn/import-style
chalkOptions.level = level;
}
const {set: setChalk} = await import('./chalk.js');
setChalk(chalkOptions);
if (confError) {
if (confError.cause) {
exit(`${confError.message}\n\n${chalk.gray(confError.cause?.stack ?? confError.cause)}`);
} else {
exit(confError.message);
}
}
const {nonSemVerExperiments: experiments, projectDir} = conf;
if (resetCache) {
const cacheDir = path.join(projectDir, 'node_modules', '.cache', 'ava');
try {
let entries;
try {
entries = fs.readdirSync(cacheDir);
} catch (error) {
if (error.code === 'ENOENT') {
entries = [];
} else {
throw error;
}
}
for (const entry of entries) {
fs.rmSync(path.join(cacheDir, entry), {recursive: true, force: true});
}
if (entries.length === 0) {
console.log(`\n${chalk.green(figures.tick)} No cache files to remove`);
} else {
console.log(`\n${chalk.green(figures.tick)} Removed AVA cache files in ${cacheDir}`);
}
process.exit(0); // eslint-disable-line unicorn/no-process-exit
} catch (error) {
exit(`Error removing AVA cache files in ${cacheDir}\n\n${chalk.gray((error && error.stack) || error)}`);
}
}
if (argv.watch) {
if (argv.tap && !conf.tap) {
exit('The TAP reporter is not available when using watch mode.');
}
if (isCi) {
exit('Watch mode is not available in CI, as it prevents AVA from terminating.');
}
if (debug !== null) {
exit('Watch mode is not available when debugging.');
}
}
if (debug !== null) {
if (argv.tap && !conf.tap) {
exit('The TAP reporter is not available when debugging.');
}
if (isCi) {
exit('Debugging is not available in CI.');
}
if (combined.timeout) {
console.log(chalk.magenta(` ${figures.warning} The timeout option has been disabled to help with debugging.`));
}
}
if (Reflect.has(combined, 'concurrency') && (!Number.isInteger(combined.concurrency) || combined.concurrency < 0)) {
exit('The --concurrency or -c flag must be provided with a nonnegative integer.');
}
if (!combined.tap && Object.keys(experiments).length > 0) {
console.log(chalk.magenta(` ${figures.warning} Experiments are enabled. These are unsupported and may change or be removed at any time.`));
}
if (Reflect.has(conf, 'babel')) {
exit('Built-in Babel support has been removed.');
}
if (Reflect.has(conf, 'compileEnhancements')) {
exit('Enhancement compilation must be configured in AVAs Babel options.');
}
if (Reflect.has(conf, 'helpers')) {
exit('AVA no longer compiles helpers. Add exclusion patterns to the files configuration and specify compileAsTests in the Babel options instead.');
}
if (Reflect.has(conf, 'sources')) {
exit('sources has been removed. Use ignoredByWatcher to provide glob patterns of files that the watcher should ignore.');
}
if (Reflect.has(conf, 'sortTestFiles') && typeof conf.sortTestFiles !== 'function') {
exit('sortTestFiles must be a comparator function.');
}
let projectPackageObject;
try {
projectPackageObject = JSON.parse(fs.readFileSync(path.resolve(projectDir, 'package.json')));
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
const {type: defaultModuleType = 'commonjs'} = projectPackageObject || {};
const providers = [];
if (Reflect.has(conf, 'typescript')) {
try {
const {level, main} = await providerManager.typescript(projectDir);
providers.push({
level,
main: main({config: conf.typescript}),
type: 'typescript',
});
} catch (error) {
exit(error.message);
}
}
let environmentVariables;
try {
environmentVariables = validateEnvironmentVariables(conf.environmentVariables);
} catch (error) {
exit(error.message);
}
let extensions;
try {
extensions = normalizeExtensions(conf.extensions, providers);
} catch (error) {
exit(error.message);
}
let moduleTypes;
try {
moduleTypes = normalizeModuleTypes(conf.extensions, defaultModuleType, experiments);
} catch (error) {
exit(error.message);
}
let globs;
try {
globs = normalizeGlobs({files: conf.files, ignoredByWatcher: conf.ignoredByWatcher, extensions, providers});
} catch (error) {
exit(error.message);
}
let nodeArguments;
try {
nodeArguments = normalizeNodeArguments(conf.nodeArguments, argv['node-arguments']);
} catch (error) {
exit(error.message);
}
let parallelRuns = null;
if (isCi && ciParallelVars && combined.utilizeParallelBuilds !== false) {
const {index: currentIndex, total: totalRuns} = ciParallelVars;
parallelRuns = {currentIndex, totalRuns};
}
const match = combined.match === '' ? [] : arrify(combined.match);
const input = debug ? debug.files : (argv.pattern || []);
const filter = input
.map(pattern => splitPatternAndLineNumbers(pattern))
.map(({pattern, ...rest}) => ({
pattern: normalizePattern(path.relative(projectDir, path.resolve(process.cwd(), pattern))),
...rest,
}));
const api = new Api({
cacheEnabled: combined.cache !== false,
chalkOptions,
concurrency: combined.concurrency || 0,
workerThreads: combined.workerThreads !== false,
debug,
environmentVariables,
experiments,
extensions,
failFast: combined.failFast,
failWithoutAssertions: combined.failWithoutAssertions !== false,
globs,
match,
moduleTypes,
nodeArguments,
parallelRuns,
sortTestFiles: conf.sortTestFiles,
projectDir,
providers,
ranFromCli: true,
require: arrify(combined.require),
serial: combined.serial,
snapshotDir: combined.snapshotDir ? path.resolve(projectDir, combined.snapshotDir) : null,
timeout: combined.timeout || '10s',
updateSnapshots: combined.updateSnapshots,
workerArgv: argv['--'],
});
const reporter = combined.tap && !combined.watch && debug === null ? new TapReporter({
extensions: globs.extensions,
projectDir,
reportStream: process.stdout,
stdStream: process.stderr,
}) : new DefaultReporter({
extensions: globs.extensions,
projectDir,
reportStream: process.stdout,
stdStream: process.stderr,
watching: combined.watch,
});
api.on('run', plan => {
reporter.startRun(plan);
if (process.env.AVA_EMIT_RUN_STATUS_OVER_IPC === 'I\'ll find a payphone baby / Take some time to talk to you') {
const bufferedSend = controlFlow(process);
plan.status.on('stateChange', evt => {
bufferedSend(evt);
});
}
plan.status.on('stateChange', evt => {
if (evt.type === 'interrupt') {
reporter.endRun();
process.exit(1); // eslint-disable-line unicorn/no-process-exit
}
});
});
if (combined.watch) {
const watcher = new Watcher({
api,
filter,
globs,
projectDir,
providers,
reporter,
});
watcher.observeStdin(process.stdin);
} else {
let debugWithoutSpecificFile = false;
api.on('run', plan => {
if (debug !== null && plan.files.length !== 1) {
debugWithoutSpecificFile = true;
}
});
const runStatus = await api.run({filter});
if (debugWithoutSpecificFile && !debug.active) {
exit('Provide the path to the test file you wish to debug');
return;
}
process.exitCode = runStatus.suggestExitCode({matching: match.length > 0});
reporter.endRun();
}
}

View File

@@ -0,0 +1,49 @@
import fs from 'node:fs';
import truncate from 'cli-truncate';
import codeExcerpt from 'code-excerpt';
import {chalk} from './chalk.js';
const formatLineNumber = (lineNumber, maxLineNumber) =>
' '.repeat(Math.max(0, String(maxLineNumber).length - String(lineNumber).length)) + lineNumber;
export default function exceptCode(source, options = {}) {
if (!source.isWithinProject || source.isDependency) {
return null;
}
const {file, line} = source;
const maxWidth = options.maxWidth || 80;
let contents;
try {
contents = fs.readFileSync(new URL(file), 'utf8');
} catch {
return null;
}
const excerpt = codeExcerpt(contents, line, {around: 1});
if (!excerpt) {
return null;
}
const lines = excerpt.map(item => ({
line: item.line,
value: truncate(item.value, maxWidth - String(line).length - 5),
}));
const extendedWidth = Math.max(...lines.map(item => item.value.length));
return lines
.map(item => {
const isErrorSource = item.line === line;
const lineNumber = formatLineNumber(item.line, line) + ':';
const coloredLineNumber = isErrorSource ? lineNumber : chalk.grey(lineNumber);
const result = ` ${coloredLineNumber} ${item.value.padEnd(extendedWidth)}`;
return isErrorSource ? chalk.bgRed.bold(result) : result;
})
.join('\n');
}

View File

@@ -0,0 +1,102 @@
import {inspect} from 'node:util';
import ansiStyles from 'ansi-styles';
import {Chalk} from 'chalk'; // eslint-disable-line unicorn/import-style
import stripAnsi from 'strip-ansi';
import {chalk} from './chalk.js';
const forceColor = new Chalk({level: Math.max(chalk.level, 1)});
const colorTheme = {
boolean: ansiStyles.yellow,
circular: forceColor.grey('[Circular]'),
date: {
invalid: forceColor.red('invalid'),
value: ansiStyles.blue,
},
diffGutters: {
actual: forceColor.red('-') + ' ',
expected: forceColor.green('+') + ' ',
padding: ' ',
},
error: {
ctor: {open: ansiStyles.grey.open + '(', close: ')' + ansiStyles.grey.close},
name: ansiStyles.magenta,
},
function: {
name: ansiStyles.blue,
stringTag: ansiStyles.magenta,
},
global: ansiStyles.magenta,
item: {after: forceColor.grey(',')},
list: {openBracket: forceColor.grey('['), closeBracket: forceColor.grey(']')},
mapEntry: {after: forceColor.grey(',')},
maxDepth: forceColor.grey('…'),
null: ansiStyles.yellow,
number: ansiStyles.yellow,
object: {
openBracket: forceColor.grey('{'),
closeBracket: forceColor.grey('}'),
ctor: ansiStyles.magenta,
stringTag: {open: ansiStyles.magenta.open + '@', close: ansiStyles.magenta.close},
secondaryStringTag: {open: ansiStyles.grey.open + '@', close: ansiStyles.grey.close},
},
property: {
after: forceColor.grey(','),
keyBracket: {open: forceColor.grey('['), close: forceColor.grey(']')},
valueFallback: forceColor.grey('…'),
},
regexp: {
source: {open: ansiStyles.blue.open + '/', close: '/' + ansiStyles.blue.close},
flags: ansiStyles.yellow,
},
stats: {separator: forceColor.grey('---')},
string: {
open: ansiStyles.blue.open,
close: ansiStyles.blue.close,
line: {open: forceColor.blue('\''), close: forceColor.blue('\'')},
multiline: {start: forceColor.blue('`'), end: forceColor.blue('`')},
controlPicture: ansiStyles.grey,
diff: {
insert: {
open: ansiStyles.bgGreen.open + ansiStyles.black.open,
close: ansiStyles.black.close + ansiStyles.bgGreen.close,
},
delete: {
open: ansiStyles.bgRed.open + ansiStyles.black.open,
close: ansiStyles.black.close + ansiStyles.bgRed.close,
},
equal: ansiStyles.blue,
insertLine: {
open: ansiStyles.green.open,
close: ansiStyles.green.close,
},
deleteLine: {
open: ansiStyles.red.open,
close: ansiStyles.red.close,
},
},
},
symbol: ansiStyles.yellow,
typedArray: {
bytes: ansiStyles.yellow,
},
undefined: ansiStyles.yellow,
};
const plainTheme = JSON.parse(JSON.stringify(colorTheme), (_name, value) => typeof value === 'string' ? stripAnsi(value) : value);
const theme = chalk.level > 0 ? colorTheme : plainTheme;
const concordanceOptions = {
// Use Node's object inspection depth, clamped to a minimum of 3
get maxDepth() {
return Math.max(3, inspect.defaultOptions.depth);
},
theme,
};
export default concordanceOptions;
export const snapshotManager = {theme: plainTheme};

View File

@@ -0,0 +1,39 @@
export default class ContextRef {
constructor() {
this.value = {};
}
get() {
return this.value;
}
set(newValue) {
this.value = newValue;
}
copy() {
return new LateBinding(this);
}
}
class LateBinding extends ContextRef {
constructor(ref) {
super();
this.ref = ref;
this.bound = false;
}
get() {
if (!this.bound) {
const value = this.ref.get();
this.set(value !== null && typeof value === 'object' ? {...value} : value);
}
return super.get();
}
set(newValue) {
this.bound = true;
super.set(newValue);
}
}

View File

@@ -0,0 +1,130 @@
const chainRegistry = new WeakMap();
function startChain(name, call, defaults) {
const fn = (...args) => {
call({...defaults}, args);
};
Object.defineProperty(fn, 'name', {value: name});
chainRegistry.set(fn, {call, defaults, fullName: name});
return fn;
}
function extendChain(previous, name, flag) {
if (!flag) {
flag = name;
}
const fn = (...args) => {
callWithFlag(previous, flag, args);
};
const fullName = `${chainRegistry.get(previous).fullName}.${name}`;
Object.defineProperty(fn, 'name', {value: fullName});
previous[name] = fn;
chainRegistry.set(fn, {flag, fullName, prev: previous});
return fn;
}
function callWithFlag(previous, flag, args) {
const combinedFlags = {[flag]: true};
do {
const step = chainRegistry.get(previous);
if (step.call) {
step.call({...step.defaults, ...combinedFlags}, args);
previous = null;
} else {
combinedFlags[step.flag] = true;
previous = step.prev;
}
} while (previous);
}
function createHookChain(hook, isAfterHook) {
// Hook chaining rules:
// * `always` comes immediately after "after hooks"
// * `skip` must come at the end
// * no `only`
// * no repeating
extendChain(hook, 'skip', 'skipped');
if (isAfterHook) {
extendChain(hook, 'always');
extendChain(hook.always, 'skip', 'skipped');
}
return hook;
}
export default function createChain(fn, defaults, meta) {
// Test chaining rules:
// * `serial` must come at the start
// * `only` and `skip` must come at the end
// * `failing` must come at the end, but can be followed by `only` and `skip`
// * `only` and `skip` cannot be chained together
// * no repeating
const root = startChain('test', fn, {...defaults, type: 'test'});
extendChain(root, 'failing');
extendChain(root, 'only', 'exclusive');
extendChain(root, 'serial');
extendChain(root, 'skip', 'skipped');
extendChain(root.failing, 'only', 'exclusive');
extendChain(root.failing, 'skip', 'skipped');
extendChain(root.serial, 'failing');
extendChain(root.serial, 'only', 'exclusive');
extendChain(root.serial, 'skip', 'skipped');
extendChain(root.serial.failing, 'only', 'exclusive');
extendChain(root.serial.failing, 'skip', 'skipped');
root.after = createHookChain(startChain('test.after', fn, {...defaults, type: 'after'}), true);
root.afterEach = createHookChain(startChain('test.afterEach', fn, {...defaults, type: 'afterEach'}), true);
root.before = createHookChain(startChain('test.before', fn, {...defaults, type: 'before'}), false);
root.beforeEach = createHookChain(startChain('test.beforeEach', fn, {...defaults, type: 'beforeEach'}), false);
root.serial.after = createHookChain(startChain('test.after', fn, {...defaults, serial: true, type: 'after'}), true);
root.serial.afterEach = createHookChain(startChain('test.afterEach', fn, {...defaults, serial: true, type: 'afterEach'}), true);
root.serial.before = createHookChain(startChain('test.before', fn, {...defaults, serial: true, type: 'before'}), false);
root.serial.beforeEach = createHookChain(startChain('test.beforeEach', fn, {...defaults, serial: true, type: 'beforeEach'}), false);
// "todo" tests cannot be chained. Allow todo tests to be flagged as needing
// to be serial.
root.todo = startChain('test.todo', fn, {...defaults, type: 'test', todo: true});
root.serial.todo = startChain('test.serial.todo', fn, {...defaults, serial: true, type: 'test', todo: true});
root.macro = options => {
if (typeof options === 'function') {
return Object.freeze({exec: options});
}
return Object.freeze({exec: options.exec, title: options.title});
};
root.meta = meta;
// The ESM and CJS type definitions export the chain (`test()` function) as
// the default. TypeScript's CJS output (when `esModuleInterop` is disabled)
// assume `require('ava').default` is available. The same goes for `import ava
// = require('ava')` syntax.
//
// Add `test.default` to make this work. Use a proxy to avoid
// `test.default.default` chains.
Object.defineProperty(root, 'default', {
configurable: false,
enumerable: false,
writable: false,
value: new Proxy(root, {
apply(target, thisArg, argumentsList) {
target.apply(thisArg, argumentsList);
},
get(target, prop) {
if (prop === 'default') {
throw new TypeError('Cannot access default.default');
}
return target[prop];
},
}),
});
return root;
}

View File

@@ -0,0 +1,13 @@
export default function validateEnvironmentVariables(environmentVariables) {
if (!environmentVariables) {
return {};
}
for (const value of Object.values(environmentVariables)) {
if (typeof value !== 'string') {
throw new TypeError('The environmentVariables configuration must be an object containing string values.');
}
}
return environmentVariables;
}

View File

@@ -0,0 +1,73 @@
import v8 from 'node:v8';
import {parentPort, workerData} from 'node:worker_threads';
import normalizeExtensions from './extensions.js';
import {normalizeGlobs} from './globs.js';
import {loadConfig} from './load-config.js';
import providerManager from './provider-manager.js';
const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs.
const configCache = new Map();
const collectProviders = async ({conf, projectDir}) => {
const providers = [];
if (Reflect.has(conf, 'typescript')) {
const {level, main} = await providerManager.typescript(projectDir);
providers.push({
level,
main: main({config: conf.typescript}),
type: 'typescript',
});
}
return providers;
};
const buildGlobs = ({conf, providers, projectDir, overrideExtensions, overrideFiles}) => {
const extensions = overrideExtensions
? normalizeExtensions(overrideExtensions)
: normalizeExtensions(conf.extensions, providers);
return {
cwd: projectDir,
...normalizeGlobs({
extensions,
files: overrideFiles || conf.files,
providers,
}),
};
};
const resolveGlobs = async (projectDir, overrideExtensions, overrideFiles) => {
if (!configCache.has(projectDir)) {
configCache.set(projectDir, loadConfig({resolveFrom: projectDir}).then(async ({config: conf}) => {
const providers = await collectProviders({conf, projectDir});
return {conf, providers};
}));
}
const {conf, providers} = await configCache.get(projectDir);
return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles});
};
const data = new Uint8Array(workerData.dataBuffer);
const sync = new Int32Array(workerData.syncBuffer);
const handleMessage = async ({projectDir, overrideExtensions, overrideFiles}) => {
let encoded;
try {
const globs = await resolveGlobs(projectDir, overrideExtensions, overrideFiles);
encoded = v8.serialize(globs);
} catch (error) {
encoded = v8.serialize(error);
}
const byteLength = encoded.length < MAX_DATA_LENGTH_EXCLUSIVE ? encoded.copy(data) : MAX_DATA_LENGTH_EXCLUSIVE;
Atomics.store(sync, 0, byteLength);
Atomics.notify(sync, 0);
};
parentPort.on('message', handleMessage);
handleMessage(workerData.firstMessage); // eslint-disable-line unicorn/prefer-top-level-await
delete workerData.firstMessage;

View File

@@ -0,0 +1,46 @@
export default function resolveExtensions(configuredExtensions, providers = []) {
// Combine all extensions possible for testing. Remove duplicate extensions.
const duplicates = new Set();
const seen = new Set();
const normalize = extensions => Array.isArray(extensions) ? extensions : Object.keys(extensions);
const combine = extensions => {
for (const ext of normalize(extensions)) {
if (seen.has(ext)) {
duplicates.add(ext);
} else {
seen.add(ext);
}
}
};
if (configuredExtensions !== undefined) {
combine(configuredExtensions);
}
for (const {main} of providers) {
combine(main.extensions);
}
if (duplicates.size > 0) {
throw new Error(`Unexpected duplicate extensions in options: ${[...duplicates].join(', ')}.`);
}
// Unless the default was used by providers, as long as the extensions aren't explicitly set, set the default.
if (configuredExtensions === undefined) {
if (!seen.has('cjs')) {
seen.add('cjs');
}
if (!seen.has('mjs')) {
seen.add('mjs');
}
if (!seen.has('js')) {
seen.add('js');
}
}
return [...seen];
}

183
github/codeql-action-v2/node_modules/ava/lib/fork.js generated vendored Normal file
View File

@@ -0,0 +1,183 @@
import childProcess from 'node:child_process';
import process from 'node:process';
import {fileURLToPath} from 'node:url';
import {Worker} from 'node:worker_threads';
import Emittery from 'emittery';
import {pEvent} from 'p-event';
import {controlFlow} from './ipc-flow-control.cjs';
import serializeError from './serialize-error.js';
let workerPath = new URL('worker/base.js', import.meta.url);
export function _testOnlyReplaceWorkerPath(replacement) {
workerPath = replacement;
}
const additionalExecArgv = ['--enable-source-maps'];
const createWorker = (options, execArgv) => {
let worker;
let postMessage;
let close;
if (options.workerThreads) {
worker = new Worker(workerPath, {
argv: options.workerArgv,
env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables},
execArgv: [...execArgv, ...additionalExecArgv],
workerData: {
options,
},
trackUnmanagedFds: true,
stdin: true,
stdout: true,
stderr: true,
});
postMessage = worker.postMessage.bind(worker);
// Ensure we've seen this event before we terminate the worker thread, as a
// workaround for https://github.com/nodejs/node/issues/38418.
const starting = pEvent(worker, 'message', ({ava}) => ava && ava.type === 'starting');
close = async () => {
try {
await starting;
await worker.terminate();
} finally {
// No-op
}
};
} else {
worker = childProcess.fork(fileURLToPath(workerPath), options.workerArgv, {
cwd: options.projectDir,
silent: true,
env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables},
execArgv: [...execArgv, ...additionalExecArgv],
});
postMessage = controlFlow(worker);
close = async () => worker.kill();
}
return {
worker,
postMessage,
close,
};
};
export default function loadFork(file, options, execArgv = process.execArgv) {
let finished = false;
const emitter = new Emittery();
const emitStateChange = evt => {
if (!finished) {
emitter.emit('stateChange', Object.assign(evt, {testFile: file}));
}
};
options = {
baseDir: process.cwd(),
file,
...options,
};
const {worker, postMessage, close} = createWorker(options, execArgv);
worker.stdout.on('data', chunk => {
emitStateChange({type: 'worker-stdout', chunk});
});
worker.stderr.on('data', chunk => {
emitStateChange({type: 'worker-stderr', chunk});
});
let forcedExit = false;
const send = evt => {
if (!finished && !forcedExit) {
postMessage({ava: evt});
}
};
const promise = new Promise(resolve => {
const finish = () => {
finished = true;
resolve();
};
worker.on('message', message => {
if (!message.ava) {
return;
}
switch (message.ava.type) {
case 'ready-for-options': {
send({type: 'options', options});
break;
}
case 'shared-worker-connect': {
const {channelId, filename, initialData, port} = message.ava;
emitter.emit('connectSharedWorker', {
filename,
initialData,
port,
signalError() {
send({type: 'shared-worker-error', channelId});
},
});
break;
}
case 'ping': {
send({type: 'pong'});
break;
}
default: {
emitStateChange(message.ava);
}
}
});
worker.on('error', error => {
emitStateChange({type: 'worker-failed', err: serializeError('Worker error', false, error, file)});
finish();
});
worker.on('exit', (code, signal) => {
if (forcedExit) {
emitStateChange({type: 'worker-finished', forcedExit});
} else if (code > 0) {
emitStateChange({type: 'worker-failed', nonZeroExitCode: code});
} else if (code === null && signal) {
emitStateChange({type: 'worker-failed', signal});
} else {
emitStateChange({type: 'worker-finished', forcedExit});
}
finish();
});
});
return {
file,
threadId: worker.threadId,
promise,
exit() {
forcedExit = true;
close();
},
notifyOfPeerFailure() {
send({type: 'peer-failed'});
},
onConnectSharedWorker(listener) {
return emitter.on('connectSharedWorker', listener);
},
onStateChange(listener) {
return emitter.on('stateChange', listener);
},
};
}

View File

@@ -0,0 +1,141 @@
'use strict';
const path = require('node:path');
const process = require('node:process');
const ignoreByDefault = require('ignore-by-default');
const picomatch = require('picomatch');
const slash = require('./slash.cjs');
const defaultIgnorePatterns = [...ignoreByDefault.directories(), '**/node_modules'];
exports.defaultIgnorePatterns = defaultIgnorePatterns;
const defaultPicomatchIgnorePatterns = [
...defaultIgnorePatterns,
// Unlike globby(), picomatch needs a complete pattern when ignoring directories.
...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`),
];
const defaultMatchNoIgnore = picomatch(defaultPicomatchIgnorePatterns);
const matchingCache = new WeakMap();
const processMatchingPatterns = input => {
let result = matchingCache.get(input);
if (!result) {
const ignore = [...defaultPicomatchIgnorePatterns];
const patterns = input.filter(pattern => {
if (pattern.startsWith('!')) {
// Unlike globby(), picomatch needs a complete pattern when ignoring directories.
ignore.push(pattern.slice(1), `${pattern.slice(1)}/**/*`);
return false;
}
return true;
});
result = {
match: picomatch(patterns, {ignore}),
matchNoIgnore: picomatch(patterns),
individualMatchers: patterns.map(pattern => ({pattern, match: picomatch(pattern, {ignore})})),
};
matchingCache.set(input, result);
}
return result;
};
exports.processMatchingPatterns = processMatchingPatterns;
const matchesIgnorePatterns = (file, patterns) => {
const {matchNoIgnore} = processMatchingPatterns(patterns);
return matchNoIgnore(file) || defaultMatchNoIgnore(file);
};
function classify(file, {cwd, extensions, filePatterns, ignoredByWatcherPatterns}) {
file = normalizeFileForMatching(cwd, file);
return {
isIgnoredByWatcher: matchesIgnorePatterns(file, ignoredByWatcherPatterns),
isTest: hasExtension(extensions, file) && !isHelperish(file) && filePatterns.length > 0 && matches(file, filePatterns),
};
}
exports.classify = classify;
const hasExtension = (extensions, file) => extensions.includes(path.extname(file).slice(1));
exports.hasExtension = hasExtension;
function isHelperish(file) { // Assume file has been normalized already.
// File names starting with an underscore are deemed "helpers".
if (path.basename(file).startsWith('_')) {
return true;
}
// This function assumes the file has been normalized. If it couldn't be,
// don't check if it's got a parent directory that starts with an underscore.
// Deem it not a "helper".
if (path.isAbsolute(file)) {
return false;
}
// If the file has a parent directory that starts with only a single
// underscore, it's deemed a "helper".
return path.dirname(file).split('/').some(dir => /^_(?:$|[^_])/.test(dir));
}
exports.isHelperish = isHelperish;
function matches(file, patterns) {
const {match} = processMatchingPatterns(patterns);
return match(file);
}
exports.matches = matches;
function normalizeFileForMatching(cwd, file) {
if (process.platform === 'win32') {
cwd = slash(cwd);
file = slash(file);
}
// Note that if `file` is outside `cwd` we can't normalize it. If this turns
// out to be a real-world scenario we may have to make changes in calling code
// to make sure the file isn't even selected for matching.
if (!file.startsWith(cwd)) {
return file;
}
// Assume `cwd` does *not* end in a slash.
return file.slice(cwd.length + 1);
}
exports.normalizeFileForMatching = normalizeFileForMatching;
function normalizePattern(pattern) {
// Always use `/` in patterns, harmonizing matching across platforms
if (process.platform === 'win32') {
pattern = slash(pattern);
}
if (pattern.endsWith('/')) {
pattern = pattern.slice(0, -1);
}
if (pattern.startsWith('./')) {
return pattern.slice(2);
}
if (pattern.startsWith('!./')) {
return `!${pattern.slice(3)}`;
}
return pattern;
}
exports.normalizePattern = normalizePattern;
function normalizePatterns(patterns) {
return patterns.map(pattern => normalizePattern(pattern));
}
exports.normalizePatterns = normalizePatterns;

216
github/codeql-action-v2/node_modules/ava/lib/globs.js generated vendored Normal file
View File

@@ -0,0 +1,216 @@
import fs from 'node:fs';
import path from 'node:path';
import {globby, globbySync} from 'globby';
import {
defaultIgnorePatterns,
hasExtension,
normalizeFileForMatching,
normalizePatterns,
processMatchingPatterns,
} from './glob-helpers.cjs';
export {
classify,
isHelperish,
matches,
normalizePattern,
defaultIgnorePatterns,
hasExtension,
normalizeFileForMatching,
normalizePatterns,
} from './glob-helpers.cjs';
const defaultIgnoredByWatcherPatterns = [
'**/*.snap.md', // No need to rerun tests when the Markdown files change.
'ava.config.js', // Config is not reloaded so avoid rerunning tests when it changes.
'ava.config.cjs', // Config is not reloaded so avoid rerunning tests when it changes.
];
const buildExtensionPattern = extensions => extensions.length === 1 ? extensions[0] : `{${extensions.join(',')}}`;
export function normalizeGlobs({extensions, files: filePatterns, ignoredByWatcher: ignoredByWatcherPatterns, providers}) {
if (filePatterns !== undefined && (!Array.isArray(filePatterns) || filePatterns.length === 0)) {
throw new Error('The files configuration must be an array containing glob patterns.');
}
if (ignoredByWatcherPatterns !== undefined && (!Array.isArray(ignoredByWatcherPatterns) || ignoredByWatcherPatterns.length === 0)) {
throw new Error('The ignoredByWatcher configuration must be an array containing glob patterns.');
}
const extensionPattern = buildExtensionPattern(extensions);
const defaultTestPatterns = [
`test.${extensionPattern}`,
`{src,source}/test.${extensionPattern}`,
`**/__tests__/**/*.${extensionPattern}`,
`**/*.spec.${extensionPattern}`,
`**/*.test.${extensionPattern}`,
`**/test-*.${extensionPattern}`,
`**/test/**/*.${extensionPattern}`,
`**/tests/**/*.${extensionPattern}`,
'!**/__tests__/**/__{helper,fixture}?(s)__/**/*',
'!**/test?(s)/**/{helper,fixture}?(s)/**/*',
];
if (filePatterns) {
filePatterns = normalizePatterns(filePatterns);
if (filePatterns.every(pattern => pattern.startsWith('!'))) {
// Use defaults if patterns only contains exclusions.
filePatterns = [...defaultTestPatterns, ...filePatterns];
}
} else {
filePatterns = defaultTestPatterns;
}
ignoredByWatcherPatterns = ignoredByWatcherPatterns ? [...defaultIgnoredByWatcherPatterns, ...normalizePatterns(ignoredByWatcherPatterns)] : [...defaultIgnoredByWatcherPatterns];
for (const {main} of providers) {
({filePatterns, ignoredByWatcherPatterns} = main.updateGlobs({filePatterns, ignoredByWatcherPatterns}));
}
return {extensions, filePatterns, ignoredByWatcherPatterns};
}
const globOptions = {
// Globs should work relative to the cwd value only (this should be the
// project directory that AVA is run in).
absolute: false,
braceExpansion: true,
caseSensitiveMatch: false,
dot: false,
expandDirectories: false,
extglob: true,
followSymbolicLinks: true,
gitignore: false,
globstar: true,
ignore: defaultIgnorePatterns,
baseNameMatch: false,
stats: false,
unique: true,
};
const globFiles = async (cwd, patterns) => {
const files = await globby(patterns, {
...globOptions,
cwd,
onlyFiles: true,
});
// Return absolute file paths. This has the side-effect of normalizing paths
// on Windows.
return files.map(file => path.join(cwd, file));
};
const globDirectoriesSync = (cwd, patterns) => {
const files = globbySync(patterns, {
...globOptions,
cwd,
onlyDirectories: true,
});
// Return absolute file paths. This has the side-effect of normalizing paths
// on Windows.
return files.map(file => path.join(cwd, file));
};
export async function findFiles({cwd, extensions, filePatterns}) {
const files = await globFiles(cwd, filePatterns);
return files.filter(file => hasExtension(extensions, file));
}
export async function findTests({cwd, extensions, filePatterns}) {
const files = await findFiles({cwd, extensions, filePatterns});
return files.filter(file => !path.basename(file).startsWith('_'));
}
export function getChokidarIgnorePatterns({ignoredByWatcherPatterns}) {
return [
...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`),
...ignoredByWatcherPatterns.filter(pattern => !pattern.startsWith('!')),
];
}
export function applyTestFileFilter({ // eslint-disable-line complexity
cwd,
expandDirectories = true,
filter,
providers = [],
testFiles,
treatFilterPatternsAsFiles = true,
}) {
const {individualMatchers} = processMatchingPatterns(filter);
const normalizedFiles = testFiles.map(file => ({file, matcheable: normalizeFileForMatching(cwd, file)}));
const selected = new Set();
const unmatchedPatterns = new Set(individualMatchers.map(({pattern}) => pattern));
for (const {pattern, match} of individualMatchers) {
for (const {file, matcheable} of normalizedFiles) {
if (match(matcheable)) {
unmatchedPatterns.delete(pattern);
selected.add(file);
}
}
}
if (expandDirectories && unmatchedPatterns.size > 0) {
const expansion = [];
for (const pattern of unmatchedPatterns) {
const directories = globDirectoriesSync(cwd, pattern);
if (directories.length > 0) {
unmatchedPatterns.delete(pattern);
expansion.push(directories);
}
}
const directories = expansion.flat();
if (directories.length > 0) {
for (const file of testFiles) {
if (selected.has(file)) {
continue;
}
for (const dir of directories) {
if (file.startsWith(dir + path.sep)) { // eslint-disable-line max-depth
selected.add(file);
}
}
}
}
}
const ignoredFilterPatternFiles = [];
if (treatFilterPatternsAsFiles && unmatchedPatterns.size > 0) {
const providerExtensions = new Set(providers.flatMap(({main}) => main.extensions));
for (const pattern of unmatchedPatterns) {
const file = path.join(cwd, pattern);
try {
const stats = fs.statSync(file);
if (!stats.isFile()) {
continue;
}
} catch (error) {
if (error.code === 'ENOENT') {
continue;
}
throw error;
}
if (
path.basename(file).startsWith('_')
|| providerExtensions.has(path.extname(file).slice(1))
|| file.split(path.sep).includes('node_modules')
) {
ignoredFilterPatternFiles.push(pattern);
continue;
}
selected.add(file);
}
}
return Object.assign([...selected], {ignoredFilterPatternFiles});
}

View File

@@ -0,0 +1,40 @@
'use strict';
function controlFlow(channel) {
let errored = false;
let deliverImmediately = true;
const backlog = [];
const deliverNext = error => {
if (error !== null) {
errored = true;
}
if (errored || !channel.connected) {
backlog.length = 0; // Free memory.
return; // We can't send.
}
let ok = true;
while (ok && backlog.length > 0) { // Stop sending after backpressure.
ok = channel.send(backlog.shift(), deliverNext);
}
// Re-enable immediate delivery if there is no backpressure and the backlog
// has been cleared.
deliverImmediately = ok && backlog.length === 0;
};
return message => {
if (errored || !channel.connected) {
return;
}
if (deliverImmediately) {
deliverImmediately = channel.send(message, deliverNext);
} else {
backlog.push(message);
}
};
}
exports.controlFlow = controlFlow;

View File

@@ -0,0 +1,7 @@
import process from 'node:process';
import info from 'ci-info';
const {AVA_FORCE_CI} = process.env;
export default AVA_FORCE_CI === 'not-ci' ? false : AVA_FORCE_CI === 'ci' || info.isCI;

View File

@@ -0,0 +1,42 @@
const isPrimitive = value => value === null || typeof value !== 'object';
export function isLikeSelector(selector) {
// Require selector to be an array or plain object.
if (
isPrimitive(selector)
|| (!Array.isArray(selector) && Reflect.getPrototypeOf(selector) !== Object.prototype)
) {
return false;
}
// Also require at least one enumerable property.
const descriptors = Object.getOwnPropertyDescriptors(selector);
return Reflect.ownKeys(descriptors).some(key => descriptors[key].enumerable === true);
}
export const CIRCULAR_SELECTOR = new Error('Encountered a circular selector');
export function selectComparable(actual, selector, circular = [selector]) {
if (isPrimitive(actual)) {
return actual;
}
const comparable = Array.isArray(selector) ? [] : {};
const enumerableKeys = Reflect.ownKeys(selector).filter(key => Reflect.getOwnPropertyDescriptor(selector, key).enumerable);
for (const key of enumerableKeys) {
const subselector = Reflect.get(selector, key);
if (isLikeSelector(subselector)) {
if (circular.includes(subselector)) {
throw CIRCULAR_SELECTOR;
}
circular.push(subselector);
comparable[key] = selectComparable(Reflect.get(actual, key), subselector, circular);
circular.pop();
} else {
comparable[key] = Reflect.get(actual, key);
}
}
return comparable;
}

View File

@@ -0,0 +1,57 @@
import picomatch from 'picomatch';
const NUMBER_REGEX = /^\d+$/;
const RANGE_REGEX = /^(?<startGroup>\d+)-(?<endGroup>\d+)$/;
const LINE_NUMBERS_REGEX = /^(?:\d+(?:-\d+)?,?)+$/;
const DELIMITER = ':';
const distinctArray = array => [...new Set(array)];
const sortNumbersAscending = array => {
const sorted = [...array];
sorted.sort((a, b) => a - b);
return sorted;
};
const parseNumber = string => Number.parseInt(string, 10);
const removeAllWhitespace = string => string.replace(/\s/g, '');
const range = (start, end) => Array.from({length: end - start + 1}).fill(start).map((element, index) => element + index);
const parseLineNumbers = suffix => sortNumbersAscending(distinctArray(
suffix.split(',').flatMap(part => {
if (NUMBER_REGEX.test(part)) {
return parseNumber(part);
}
const {groups: {startGroup, endGroup}} = RANGE_REGEX.exec(part);
const start = parseNumber(startGroup);
const end = parseNumber(endGroup);
if (start > end) {
return range(end, start);
}
return range(start, end);
}),
));
export function splitPatternAndLineNumbers(pattern) {
const parts = pattern.split(DELIMITER);
if (parts.length === 1) {
return {pattern, lineNumbers: null};
}
const suffix = removeAllWhitespace(parts.pop());
if (!LINE_NUMBERS_REGEX.test(suffix)) {
return {pattern, lineNumbers: null};
}
return {pattern: parts.join(DELIMITER), lineNumbers: parseLineNumbers(suffix)};
}
export function getApplicableLineNumbers(normalizedFilePath, filter) {
return sortNumbersAscending(distinctArray(
filter
.filter(({pattern, lineNumbers}) => lineNumbers && picomatch.isMatch(normalizedFilePath, pattern))
.flatMap(({lineNumbers}) => lineNumbers),
));
}

View File

@@ -0,0 +1,170 @@
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import url from 'node:url';
import {isPlainObject} from 'is-plain-object';
import {packageConfig, packageJsonPath} from 'pkg-conf';
const NO_SUCH_FILE = Symbol('no ava.config.js file');
const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
const EXPERIMENTS = new Set();
const importConfig = async ({configFile, fileForErrorMessage}) => {
const {default: config = MISSING_DEFAULT_EXPORT} = await import(url.pathToFileURL(configFile));
if (config === MISSING_DEFAULT_EXPORT) {
throw new Error(`${fileForErrorMessage} must have a default export`);
}
return config;
};
const loadConfigFile = async ({projectDir, configFile}) => {
const fileForErrorMessage = path.relative(projectDir, configFile);
try {
await fs.promises.access(configFile);
return {config: await importConfig({configFile, fileForErrorMessage}), configFile, fileForErrorMessage};
} catch (error) {
if (error.code === 'ENOENT') {
return null;
}
throw Object.assign(new Error(`Error loading ${fileForErrorMessage}: ${error.message}`), {cause: error});
}
};
function resolveConfigFile(configFile) {
if (configFile) {
configFile = path.resolve(configFile); // Relative to CWD
}
return configFile;
}
const gitScmFile = process.env.AVA_FAKE_SCM_ROOT || '.git';
async function findRepoRoot(fromDir) {
const {root} = path.parse(fromDir);
let dir = fromDir;
while (root !== dir) {
try {
const stat = await fs.promises.stat(path.join(dir, gitScmFile)); // eslint-disable-line no-await-in-loop
if (stat.isFile() || stat.isDirectory()) {
return dir;
}
} catch {}
dir = path.dirname(dir);
}
return root;
}
async function checkJsonFile(searchDir) {
const file = path.join(searchDir, 'ava.config.json');
try {
await fs.promises.access(file);
return file;
} catch (error) {
if (error.code === 'ENOENT') {
return null;
}
throw error;
}
}
export async function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) { // eslint-disable-line complexity
let packageConf = await packageConfig('ava', {cwd: resolveFrom});
const filepath = packageJsonPath(packageConf);
const projectDir = filepath === undefined ? resolveFrom : path.dirname(filepath);
const repoRoot = await findRepoRoot(projectDir);
// Conflicts are only allowed when an explicit config file is provided.
const allowConflictWithPackageJson = Boolean(configFile);
configFile = resolveConfigFile(configFile);
const unsupportedFiles = [];
let fileConf = NO_SUCH_FILE;
let fileForErrorMessage;
let conflicting = [];
if (configFile) {
let loaded;
try {
loaded = await loadConfigFile({projectDir, configFile});
} catch (error) {
if (!configFile.endsWith('.js') && !configFile.endsWith('.cjs') && !configFile.endsWith('.mjs')) {
throw Object.assign(new Error('Could not load config file; it should have .js, .cjs or .mjs extension'), {cause: error});
}
throw error;
}
if (loaded !== null) {
({config: fileConf, fileForErrorMessage} = loaded);
}
} else {
let searchDir = projectDir;
const stopAt = path.dirname(repoRoot);
do {
const [jsonFile, ...results] = await Promise.all([ // eslint-disable-line no-await-in-loop
checkJsonFile(searchDir),
loadConfigFile({projectDir, configFile: path.join(searchDir, 'ava.config.js')}),
loadConfigFile({projectDir, configFile: path.join(searchDir, 'ava.config.cjs')}),
loadConfigFile({projectDir, configFile: path.join(searchDir, 'ava.config.mjs')}),
]);
if (jsonFile !== null) {
unsupportedFiles.push(jsonFile);
}
[{config: fileConf, fileForErrorMessage, configFile} = {config: NO_SUCH_FILE, fileForErrorMessage: undefined}, ...conflicting] = results.filter(result => result !== null);
searchDir = path.dirname(searchDir);
} while (fileConf === NO_SUCH_FILE && searchDir !== stopAt);
}
if (conflicting.length > 0) {
throw new Error(`Conflicting configuration in ${fileForErrorMessage} and ${conflicting.map(({fileForErrorMessage}) => fileForErrorMessage).join(' & ')}`);
}
if (fileConf !== NO_SUCH_FILE) {
if (allowConflictWithPackageJson) {
packageConf = {};
} else if (Object.keys(packageConf).length > 0) {
throw new Error(`Conflicting configuration in ${fileForErrorMessage} and package.json`);
}
if (!isPlainObject(fileConf) && typeof fileConf !== 'function') {
throw new TypeError(`${fileForErrorMessage} must export a plain object or factory function`);
}
if (typeof fileConf === 'function') {
fileConf = await fileConf({projectDir});
if (!isPlainObject(fileConf)) {
throw new TypeError(`Factory method exported by ${fileForErrorMessage} must return a plain object`);
}
}
if ('ava' in fileConf) {
throw new Error(`Encountered ava property in ${fileForErrorMessage}; avoid wrapping the configuration`);
}
}
const config = {...defaults, nonSemVerExperiments: {}, ...fileConf, ...packageConf, projectDir, configFile};
const {nonSemVerExperiments: experiments} = config;
if (!isPlainObject(experiments)) {
throw new Error(`nonSemVerExperiments from ${fileForErrorMessage} must be an object`);
}
for (const key of Object.keys(experiments)) {
if (!EXPERIMENTS.has(key)) {
throw new Error(`nonSemVerExperiments.${key} from ${fileForErrorMessage} is not a supported experiment`);
}
}
return {config, unsupportedFiles};
}

View File

@@ -0,0 +1,85 @@
const requireTrueValue = value => {
if (value !== true) {
throw new TypeError('When specifying module types, use `true` for cjs, mjs and js extensions');
}
};
const normalize = (extension, type, defaultModuleType) => {
switch (extension) {
case 'cjs': {
requireTrueValue(type);
return 'commonjs';
}
case 'mjs': {
requireTrueValue(type);
return 'module';
}
case 'js': {
requireTrueValue(type);
return defaultModuleType;
}
default: {
if (type !== 'commonjs' && type !== 'module') {
throw new TypeError(`Module type for ${extension} must be commonjs or module`);
}
return type;
}
}
};
const deriveFromObject = (extensionsObject, defaultModuleType) => {
const moduleTypes = {};
for (const [extension, type] of Object.entries(extensionsObject)) {
moduleTypes[extension] = normalize(extension, type, defaultModuleType);
}
return moduleTypes;
};
const deriveFromArray = (extensions, defaultModuleType) => {
const moduleTypes = {};
for (const extension of extensions) {
switch (extension) {
case 'cjs': {
moduleTypes.cjs = 'commonjs';
break;
}
case 'mjs': {
moduleTypes.mjs = 'module';
break;
}
case 'js': {
moduleTypes.js = defaultModuleType;
break;
}
default: {
moduleTypes[extension] = 'commonjs';
}
}
}
return moduleTypes;
};
export default function moduleTypes(configuredExtensions, defaultModuleType) {
if (configuredExtensions === undefined) {
return {
cjs: 'commonjs',
mjs: 'module',
js: defaultModuleType,
};
}
if (Array.isArray(configuredExtensions)) {
return deriveFromArray(configuredExtensions, defaultModuleType);
}
return deriveFromObject(configuredExtensions, defaultModuleType);
}

View File

@@ -0,0 +1,16 @@
import process from 'node:process';
import arrgv from 'arrgv';
export default function normalizeNodeArguments(fromConf = [], fromArgv = '') {
let parsedArgv = [];
if (fromArgv !== '') {
try {
parsedArgv = arrgv(fromArgv);
} catch {
throw new Error('Could not parse `--node-arguments` value. Make sure all strings are closed and backslashes are used correctly.');
}
}
return [...process.execArgv, ...fromConf, ...parsedArgv];
}

View File

@@ -0,0 +1,16 @@
'use strict';
const timers = require('node:timers');
Object.assign(exports, timers);
exports.now = Date.now;
// Any delay larger than this value is ignored by Node.js, with a delay of `1`
// used instead. See <https://nodejs.org/api/timers.html#settimeoutcallback-delay-args>.
const MAX_DELAY = (2 ** 31) - 1;
function setCappedTimeout(callback, delay) {
const safeDelay = Math.min(delay, MAX_DELAY);
return timers.setTimeout(callback, safeDelay);
}
exports.setCappedTimeout = setCappedTimeout;

View File

@@ -0,0 +1,26 @@
const buildTitle = (raw, implementation, args) => {
let value = implementation && implementation.title ? implementation.title(raw, ...args) : raw;
const isValid = typeof value === 'string';
if (isValid) {
value = value.trim().replace(/\s+/g, ' ');
}
return {
raw,
value,
isSet: value !== undefined,
isValid,
isEmpty: !isValid || value === '',
};
};
export default function parseTestArgs(args) {
const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined;
const implementation = args.shift();
return {
args,
implementation: implementation && implementation.exec ? implementation.exec : implementation,
title: buildTitle(rawTitle, implementation, args),
};
}

2
github/codeql-action-v2/node_modules/ava/lib/pkg.cjs generated vendored Normal file
View File

@@ -0,0 +1,2 @@
'use strict';
module.exports = require('../package.json');

View File

@@ -0,0 +1,248 @@
import {EventEmitter, on} from 'node:events';
import process from 'node:process';
import {workerData, parentPort, threadId} from 'node:worker_threads';
import pkg from '../pkg.cjs';
// Used to forward messages received over the `parentPort` and any direct ports
// to test workers. Every subscription adds a listener, so do not enforce any
// maximums.
const events = new EventEmitter().setMaxListeners(0);
const emitMessage = message => {
// Wait for a turn of the event loop, to allow new subscriptions to be
// set up in response to the previous message.
setImmediate(() => events.emit('message', message));
};
// Map of active test workers, used in receiveMessages() to get a reference to
// the TestWorker instance, and relevant release functions.
const activeTestWorkers = new Map();
const internalMessagePort = Symbol('Internal MessagePort');
class TestWorker {
constructor(id, file, port) {
this.id = id;
this.file = file;
this[internalMessagePort] = port;
}
teardown(fn) {
let done = false;
const teardownFn = async () => {
if (done) {
return;
}
done = true;
if (activeTestWorkers.has(this.id)) {
activeTestWorkers.get(this.id).teardownFns.delete(teardownFn);
}
await fn();
};
activeTestWorkers.get(this.id).teardownFns.add(teardownFn);
return teardownFn;
}
publish(data) {
return publishMessage(this, data);
}
async * subscribe() {
yield * receiveMessages(this);
}
}
class ReceivedMessage {
constructor(testWorker, id, data) {
this.testWorker = testWorker;
this.id = id;
this.data = data;
}
reply(data) {
return publishMessage(this.testWorker, data, this.id);
}
}
// Ensure that, no matter how often it's received, we have a stable message
// object.
const messageCache = new WeakMap();
async function * receiveMessages(fromTestWorker, replyTo) {
for await (const [message] of on(events, 'message')) {
if (fromTestWorker !== undefined) {
if (message.type === 'deregister-test-worker' && message.id === fromTestWorker.id) {
return;
}
if (message.type === 'message' && message.testWorkerId !== fromTestWorker.id) {
continue;
}
}
if (message.type !== 'message') {
continue;
}
if (replyTo === undefined && message.replyTo !== undefined) {
continue;
}
if (replyTo !== undefined && message.replyTo !== replyTo) {
continue;
}
const active = activeTestWorkers.get(message.testWorkerId);
// It is possible for a message to have been buffering for so long — perhaps
// due to the caller waiting before iterating to the next message — that the
// test worker has been deregistered. Ignore such messages.
//
// (This is really hard to write a test for, however!)
if (active === undefined) {
continue;
}
let received = messageCache.get(message);
if (received === undefined) {
received = new ReceivedMessage(active.instance, message.messageId, message.data);
messageCache.set(message, received);
}
yield received;
}
}
let messageCounter = 0;
const messageIdPrefix = `${threadId}/message`;
const nextMessageId = () => `${messageIdPrefix}/${++messageCounter}`;
function publishMessage(testWorker, data, replyTo) {
const id = nextMessageId();
testWorker[internalMessagePort].postMessage({
type: 'message',
messageId: id,
data,
replyTo,
});
return {
id,
async * replies() {
yield * receiveMessages(testWorker, id);
},
};
}
function broadcastMessage(data) {
const id = nextMessageId();
for (const trackedWorker of activeTestWorkers.values()) {
trackedWorker.instance[internalMessagePort].postMessage({
type: 'message',
messageId: id,
data,
});
}
return {
id,
async * replies() {
yield * receiveMessages(undefined, id);
},
};
}
async function loadFactory() {
const {default: factory} = await import(workerData.filename);
return factory;
}
let signalAvailable = () => {
parentPort.postMessage({type: 'available'});
signalAvailable = () => {};
};
let fatal;
try {
const factory = await loadFactory(workerData.filename);
if (typeof factory !== 'function') {
throw new TypeError(`Missing default factory function export for shared worker plugin at ${workerData.filename}`);
}
factory({
negotiateProtocol(supported) {
if (!supported.includes('ava-4')) {
fatal = new Error(`This version of AVA (${pkg.version}) is not compatible with shared worker plugin at ${workerData.filename}`);
throw fatal;
}
const produceTestWorker = instance => events.emit('testWorker', instance);
parentPort.on('message', async message => {
if (message.type === 'register-test-worker') {
const {id, file, port} = message;
const instance = new TestWorker(id, file, port);
activeTestWorkers.set(id, {instance, teardownFns: new Set()});
produceTestWorker(instance);
port.on('message', message => emitMessage({testWorkerId: id, ...message}));
}
if (message.type === 'deregister-test-worker') {
const {id} = message;
const {teardownFns} = activeTestWorkers.get(id);
activeTestWorkers.delete(id);
// Run possibly asynchronous release functions serially, in reverse
// order. Any error will crash the worker.
for await (const fn of [...teardownFns].reverse()) {
await fn();
}
parentPort.postMessage({
type: 'deregistered-test-worker',
id,
});
emitMessage(message);
}
});
return {
initialData: workerData.initialData,
protocol: 'ava-4',
ready() {
signalAvailable();
return this;
},
broadcast(data) {
return broadcastMessage(data);
},
async * subscribe() {
yield * receiveMessages();
},
async * testWorkers() {
for await (const [worker] of on(events, 'testWorker')) {
yield worker;
}
},
};
},
});
} catch (error) {
fatal = fatal ?? error;
} finally {
if (fatal !== undefined) {
process.nextTick(() => {
throw fatal;
});
}
}

View File

@@ -0,0 +1,128 @@
import events from 'node:events';
import {pathToFileURL} from 'node:url';
import {Worker} from 'node:worker_threads';
import serializeError from '../serialize-error.js';
const LOADER = new URL('shared-worker-loader.js', import.meta.url);
const launchedWorkers = new Map();
const waitForAvailable = async worker => {
for await (const [message] of events.on(worker, 'message')) {
if (message.type === 'available') {
return;
}
}
};
function launchWorker(filename, initialData) {
if (launchedWorkers.has(filename)) {
return launchedWorkers.get(filename);
}
const worker = new Worker(LOADER, {
// Ensure the worker crashes for unhandled rejections, rather than allowing undefined behavior.
execArgv: ['--unhandled-rejections=strict'],
workerData: {
filename,
initialData,
},
});
worker.setMaxListeners(0);
const launched = {
statePromises: {
available: waitForAvailable(worker),
error: events.once(worker, 'error').then(([error]) => error),
},
exited: false,
worker,
};
launchedWorkers.set(filename, launched);
worker.once('exit', () => {
launched.exited = true;
});
return launched;
}
export async function observeWorkerProcess(fork, runStatus) {
let signalDone;
const done = new Promise(resolve => {
signalDone = () => {
resolve();
};
});
const activeInstances = new Set();
const removeInstance = instance => {
instance.worker.unref();
activeInstances.delete(instance);
if (activeInstances.size === 0) {
signalDone();
}
};
const removeAllInstances = () => {
if (activeInstances.size === 0) {
signalDone();
return;
}
for (const instance of activeInstances) {
removeInstance(instance);
}
};
fork.promise.finally(() => {
removeAllInstances();
});
fork.onConnectSharedWorker(async ({filename, initialData, port, signalError}) => {
const launched = launchWorker(filename, initialData);
activeInstances.add(launched);
const handleWorkerMessage = async message => {
if (message.type === 'deregistered-test-worker' && message.id === fork.threadId) {
launched.worker.off('message', handleWorkerMessage);
removeInstance(launched);
}
};
launched.statePromises.error.then(error => {
launched.worker.off('message', handleWorkerMessage);
removeAllInstances();
runStatus.emitStateChange({type: 'shared-worker-error', err: serializeError('Shared worker error', true, error)});
signalError();
});
try {
await launched.statePromises.available;
port.postMessage({type: 'ready'});
launched.worker.postMessage({
type: 'register-test-worker',
id: fork.threadId,
file: pathToFileURL(fork.file).toString(),
port,
}, [port]);
fork.promise.finally(() => {
launched.worker.postMessage({
type: 'deregister-test-worker',
id: fork.threadId,
});
});
launched.worker.on('message', handleWorkerMessage);
} catch {}
});
return done;
}

View File

@@ -0,0 +1,59 @@
import * as globs from './globs.js';
import pkg from './pkg.cjs';
const levels = {
// As the protocol changes, comparing levels by integer allows AVA to be
// compatible with different versions. Currently there is only one supported
// version, so this is effectively unused. The infrastructure is retained for
// future use.
levelIntegersAreCurrentlyUnused: 0,
};
const levelsByProtocol = {
'ava-3.2': levels.levelIntegersAreCurrentlyUnused,
};
async function load(providerModule, projectDir) {
const ava = {version: pkg.version};
const {default: makeProvider} = await import(providerModule);
let fatal;
let level;
const provider = makeProvider({
negotiateProtocol(identifiers, {version}) {
const identifier = identifiers.find(identifier => Reflect.has(levelsByProtocol, identifier));
if (identifier === undefined) {
fatal = new Error(`This version of AVA (${ava.version}) is not compatible with ${providerModule}@${version}`);
return null;
}
level = levelsByProtocol[identifier];
return {
ava,
async findFiles({extensions, patterns}) {
return globs.findFiles({cwd: projectDir, extensions, filePatterns: patterns});
},
identifier,
normalizeGlobPatterns: globs.normalizePatterns,
projectDir,
};
},
});
if (fatal) {
throw fatal;
}
return {...provider, level};
}
const providerManager = {
levels,
async typescript(projectDir) {
return load('@ava/typescript', projectDir);
},
};
export default providerManager;

View File

@@ -0,0 +1,67 @@
import StackUtils from 'stack-utils';
const stackUtils = new StackUtils({
ignoredPackages: [
'@ava/typescript',
'ava',
'nyc',
],
internals: [
// AVA internals, which ignoredPackages don't ignore when we run our own unit tests.
/\/ava\/(?:lib\/|lib\/worker\/)?[\w-]+\.js:\d+:\d+\)?$/,
// Only ignore Node.js internals that really are not useful for debugging.
...StackUtils.nodeInternals().filter(regexp => !/\(internal/.test(regexp.source)),
/\(internal\/process\/task_queues\.js:\d+:\d+\)$/,
/\(internal\/modules\/cjs\/.+?\.js:\d+:\d+\)$/,
/async Promise\.all \(index/,
/new Promise \(<anonymous>\)/,
],
});
/*
* Given a string value of the format generated for the `stack` property of a
* V8 error object, return a string that contains only stack frame information
* for frames that have relevance to the consumer.
*
* For example, given the following string value:
*
* ```
* Error
* at inner (/home/ava/ex.js:7:12)
* at /home/ava/ex.js:12:5
* at outer (/home/ava/ex.js:13:4)
* at Object.<anonymous> (/home/ava/ex.js:14:3)
* at Module._compile (module.js:570:32)
* at Object.Module._extensions..js (module.js:579:10)
* at Module.load (module.js:487:32)
* at tryModuleLoad (module.js:446:12)
* at Function.Module._load (module.js:438:3)
* at Module.runMain (module.js:604:10)
* ```
*
* ...this function returns the following string value:
*
* ```
* inner (/home/ava/ex.js:7:12)
* /home/ava/ex.js:12:5
* outer (/home/ava/ex.js:13:4)
* Object.<anonymous> (/home/ava/ex.js:14:3)
* Module._compile (module.js:570:32)
* Object.Module._extensions..js (module.js:579:10)
* Module.load (module.js:487:32)
* tryModuleLoad (module.js:446:12)
* Function.Module._load (module.js:438:3)
* Module.runMain (module.js:604:10)
* ```
*/
export default function beautifyStack(stack) {
if (!stack) {
return [];
}
return stackUtils.clean(stack)
.trim()
.split('\n')
.map(line => line.trim())
.filter(line => line !== '');
}

View File

@@ -0,0 +1,42 @@
import {chalk} from '../chalk.js';
const colors = {
get log() {
return chalk.gray;
},
get title() {
return chalk.bold;
},
get error() {
return chalk.red;
},
get skip() {
return chalk.yellow;
},
get todo() {
return chalk.blue;
},
get pass() {
return chalk.green;
},
get duration() {
return chalk.gray.dim;
},
get errorSource() {
return chalk.gray;
},
get errorStack() {
return chalk.gray;
},
get errorStackInternal() {
return chalk.gray.dim;
},
get stack() {
return chalk.red;
},
get information() {
return chalk.magenta;
},
};
export default colors;

View File

@@ -0,0 +1,701 @@
import os from 'node:os';
import path from 'node:path';
import stream from 'node:stream';
import {fileURLToPath} from 'node:url';
import figures from 'figures';
import indentString from 'indent-string';
import plur from 'plur';
import prettyMs from 'pretty-ms';
import StackUtils from 'stack-utils';
import {chalk} from '../chalk.js';
import codeExcerpt from '../code-excerpt.js';
import beautifyStack from './beautify-stack.js';
import colors from './colors.js';
import formatSerializedError from './format-serialized-error.js';
import improperUsageMessage from './improper-usage-messages.js';
import prefixTitle from './prefix-title.js';
const nodeInternals = StackUtils.nodeInternals();
class LineWriter extends stream.Writable {
constructor(dest) {
super();
this.dest = dest;
this.columns = dest.columns || 80;
this.lastLineIsEmpty = false;
}
_write(chunk, _, callback) {
this.dest.write(chunk);
callback();
}
writeLine(string) {
if (string) {
this.write(indentString(string, 2) + os.EOL);
this.lastLineIsEmpty = false;
} else {
this.write(os.EOL);
this.lastLineIsEmpty = true;
}
}
ensureEmptyLine() {
if (!this.lastLineIsEmpty) {
this.writeLine();
}
}
}
function manageCorking(stream) {
return {
decorateWriter(fn) {
return function (...args) {
stream.cork();
try {
return fn.apply(this, args);
} finally {
stream.uncork();
}
};
},
};
}
export default class Reporter {
constructor({
extensions,
reportStream,
stdStream,
projectDir,
watching,
durationThreshold,
}) {
this.extensions = extensions;
this.reportStream = reportStream;
this.stdStream = stdStream;
this.watching = watching;
this.relativeFile = file => {
if (file.startsWith('file://')) {
file = fileURLToPath(file);
}
return path.relative(projectDir, file);
};
const {decorateWriter} = manageCorking(this.reportStream);
this.consumeStateChange = decorateWriter(this.consumeStateChange);
this.endRun = decorateWriter(this.endRun);
this.durationThreshold = durationThreshold || 100;
this.lineWriter = new LineWriter(this.reportStream);
this.reset();
}
reset() {
if (this.removePreviousListener) {
this.removePreviousListener();
}
this.prefixTitle = (testFile, title) => title;
this.runningTestFiles = new Map();
this.filesWithMissingAvaImports = new Set();
this.filesWithoutDeclaredTests = new Set();
this.filesWithoutMatchedLineNumbers = new Set();
this.failures = [];
this.internalErrors = [];
this.knownFailures = [];
this.lineNumberErrors = [];
this.sharedWorkerErrors = [];
this.uncaughtExceptions = [];
this.unhandledRejections = [];
this.previousFailures = 0;
this.failFastEnabled = false;
this.lastLineIsEmpty = false;
this.matching = false;
this.removePreviousListener = null;
this.stats = null;
}
startRun(plan) {
if (plan.bailWithoutReporting) {
return;
}
this.reset();
this.failFastEnabled = plan.failFastEnabled;
this.matching = plan.matching;
this.previousFailures = plan.previousFailures;
this.emptyParallelRun = plan.status.emptyParallelRun;
this.selectionInsights = plan.status.selectionInsights;
if (this.watching || plan.files.length > 1) {
this.prefixTitle = (testFile, title) => prefixTitle(this.extensions, plan.filePathPrefix, testFile, title);
}
this.removePreviousListener = plan.status.on('stateChange', evt => {
this.consumeStateChange(evt);
});
if (this.watching && plan.runVector > 1) {
this.lineWriter.write(chalk.gray.dim('\u2500'.repeat(this.lineWriter.columns)) + os.EOL);
}
this.lineWriter.writeLine();
}
consumeStateChange(event) { // eslint-disable-line complexity
const fileStats = this.stats && event.testFile ? this.stats.byFile.get(event.testFile) : null;
switch (event.type) { // eslint-disable-line default-case
case 'hook-failed': {
this.failures.push(event);
this.writeTestSummary(event);
break;
}
case 'stats': {
this.stats = event.stats;
break;
}
case 'test-failed': {
this.failures.push(event);
this.writeTestSummary(event);
break;
}
case 'test-passed': {
if (event.knownFailing) {
this.knownFailures.push(event);
}
this.writeTestSummary(event);
break;
}
case 'timeout': {
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Timed out while running tests`));
this.lineWriter.writeLine('');
this.writePendingTests(event);
break;
}
case 'interrupt': {
this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exiting due to SIGINT`));
this.lineWriter.writeLine('');
this.writePendingTests(event);
break;
}
case 'internal-error': {
this.internalErrors.push(event);
if (event.testFile) {
this.write(colors.error(`${figures.cross} Internal error when running ${this.relativeFile(event.testFile)}`));
} else {
this.write(colors.error(`${figures.cross} Internal error`));
}
this.lineWriter.writeLine(colors.stack(event.err.summary));
this.lineWriter.writeLine(colors.errorStack(event.err.stack));
this.lineWriter.writeLine();
this.lineWriter.writeLine();
break;
}
case 'line-number-selection-error': {
this.lineNumberErrors.push(event);
this.write(colors.information(`${figures.warning} Could not parse ${this.relativeFile(event.testFile)} for line number selection`));
this.lineWriter.writeLine();
this.lineWriter.writeLine(colors.errorStack(event.err.stack));
this.lineWriter.writeLine();
break;
}
case 'missing-ava-import': {
this.filesWithMissingAvaImports.add(event.testFile);
this.write(colors.error(`${figures.cross} No tests found in ${this.relativeFile(event.testFile)}, make sure to import "ava" at the top of your test file`));
break;
}
case 'process-exit': {
this.write(colors.error(`${figures.cross} Exiting due to process.exit() when running ${this.relativeFile(event.testFile)}`));
this.lineWriter.writeLine();
this.lineWriter.writeLine(colors.errorStack(event.stack));
this.lineWriter.writeLine();
break;
}
case 'hook-finished': {
if (event.logs.length > 0) {
this.lineWriter.writeLine(` ${this.prefixTitle(event.testFile, event.title)}`);
this.writeLogs(event);
}
break;
}
case 'selected-test': {
if (event.skip) {
this.lineWriter.writeLine(colors.skip(`- [skip] ${this.prefixTitle(event.testFile, event.title)}`));
} else if (event.todo) {
this.lineWriter.writeLine(colors.todo(`- [todo] ${this.prefixTitle(event.testFile, event.title)}`));
}
break;
}
case 'shared-worker-error': {
this.sharedWorkerErrors.push(event);
this.lineWriter.ensureEmptyLine();
this.lineWriter.writeLine(colors.error(`${figures.cross} Error in shared worker`));
this.lineWriter.writeLine();
this.writeErr(event);
break;
}
case 'uncaught-exception': {
this.uncaughtExceptions.push(event);
this.lineWriter.ensureEmptyLine();
this.lineWriter.writeLine(colors.title(`Uncaught exception in ${this.relativeFile(event.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(event);
break;
}
case 'unhandled-rejection': {
this.unhandledRejections.push(event);
this.lineWriter.ensureEmptyLine();
this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${this.relativeFile(event.testFile)}`));
this.lineWriter.writeLine();
this.writeErr(event);
break;
}
case 'worker-failed': {
if (fileStats.declaredTests === 0) {
this.filesWithoutDeclaredTests.add(event.testFile);
}
if (!this.filesWithMissingAvaImports.has(event.testFile)) {
if (event.err) {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(event.testFile)} exited due to an error:`));
this.lineWriter.writeLine();
this.writeErr(event);
} else if (event.nonZeroExitCode) {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(event.testFile)} exited with a non-zero exit code: ${event.nonZeroExitCode}`));
} else {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(event.testFile)} exited due to ${event.signal}`));
}
}
break;
}
case 'worker-finished': {
if (!event.forcedExit && !this.filesWithMissingAvaImports.has(event.testFile)) {
if (fileStats.declaredTests === 0) {
this.filesWithoutDeclaredTests.add(event.testFile);
this.write(colors.error(`${figures.cross} No tests found in ${this.relativeFile(event.testFile)}`));
} else if (fileStats.selectingLines && fileStats.selectedTests === 0) {
this.filesWithoutMatchedLineNumbers.add(event.testFile);
this.lineWriter.writeLine(colors.error(`${figures.cross} Line numbers for ${this.relativeFile(event.testFile)} did not match any tests`));
} else if (!this.failFastEnabled && fileStats.remainingTests > 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} ${fileStats.remainingTests} ${plur('test', fileStats.remainingTests)} remaining in ${this.relativeFile(event.testFile)}`));
}
}
break;
}
case 'worker-stderr': {
this.stdStream.write(event.chunk);
// If the chunk does not end with a linebreak, *forcibly* write one to
// ensure it remains visible in the TTY.
// Tests cannot assume their standard output is not interrupted. Indeed
// we multiplex stdout and stderr into a single stream. However as
// long as stdStream is different from reportStream users can read
// their original output by redirecting the streams.
if (event.chunk[event.chunk.length - 1] !== 0x0A) {
this.reportStream.write(os.EOL);
}
break;
}
case 'worker-stdout': {
this.stdStream.write(event.chunk);
// If the chunk does not end with a linebreak, *forcibly* write one to
// ensure it remains visible in the TTY.
// Tests cannot assume their standard output is not interrupted. Indeed
// we multiplex stdout and stderr into a single stream. However as
// long as stdStream is different from reportStream users can read
// their original output by redirecting the streams.
if (event.chunk[event.chunk.length - 1] !== 0x0A) {
this.reportStream.write(os.EOL);
}
}
}
}
writePendingTests(evt) {
for (const [file, testsInFile] of evt.pendingTests) {
if (testsInFile.size === 0) {
continue;
}
this.lineWriter.writeLine(`${testsInFile.size} tests were pending in ${this.relativeFile(file)}\n`);
const testTitleToLogs = evt.pendingTestsLogs.get(file);
for (const title of testsInFile) {
const logs = testTitleToLogs?.get(title);
this.lineWriter.writeLine(`${figures.circleDotted} ${this.prefixTitle(file, title)}`);
this.writeLogs({logs});
}
this.lineWriter.writeLine('');
}
}
write(string) {
this.lineWriter.writeLine(string);
}
writeWithCounts(string) {
if (!this.stats) {
return this.lineWriter.writeLine(string);
}
string = string || '';
if (string !== '') {
string += os.EOL;
}
let firstLinePostfix = this.watching ? ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') : '';
if (this.stats.passedTests > 0) {
string += os.EOL + colors.pass(`${this.stats.passedTests} passed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.passedKnownFailingTests > 0) {
string += os.EOL + colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`);
}
if (this.stats.failedHooks > 0) {
string += os.EOL + colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.failedTests > 0) {
string += os.EOL + colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix;
firstLinePostfix = '';
}
if (this.stats.skippedTests > 0) {
string += os.EOL + colors.skip(`${this.stats.skippedTests} skipped`);
}
if (this.stats.todoTests > 0) {
string += os.EOL + colors.todo(`${this.stats.todoTests} todo`);
}
this.lineWriter.writeLine(string);
}
writeErr(event) {
if (event.err.name === 'TSError' && event.err.object && event.err.object.diagnosticText) {
this.lineWriter.writeLine(colors.errorStack(event.err.object.diagnosticText));
this.lineWriter.writeLine();
return;
}
if (event.err.source) {
this.lineWriter.writeLine(colors.errorSource(`${this.relativeFile(event.err.source.file)}:${event.err.source.line}`));
const excerpt = codeExcerpt(event.err.source, {maxWidth: this.reportStream.columns - 2});
if (excerpt) {
this.lineWriter.writeLine();
this.lineWriter.writeLine(excerpt);
this.lineWriter.writeLine();
}
}
if (event.err.avaAssertionError) {
const result = formatSerializedError(event.err);
if (result.printMessage) {
this.lineWriter.writeLine(event.err.message);
this.lineWriter.writeLine();
}
if (result.formatted) {
this.lineWriter.writeLine(result.formatted);
this.lineWriter.writeLine();
}
const message = improperUsageMessage(event.err);
if (message) {
this.lineWriter.writeLine(message);
this.lineWriter.writeLine();
}
} else if (event.err.nonErrorObject) {
this.lineWriter.writeLine(event.err.formatted);
this.lineWriter.writeLine();
} else {
this.lineWriter.writeLine(event.err.summary);
this.lineWriter.writeLine();
}
const formatted = this.formatErrorStack(event.err);
if (formatted.length > 0) {
this.lineWriter.writeLine(formatted.join('\n'));
this.lineWriter.writeLine();
}
}
formatErrorStack(error) {
if (!error.stack) {
return [];
}
if (error.shouldBeautifyStack) {
return beautifyStack(error.stack).map(line => {
if (nodeInternals.some(internal => internal.test(line))) {
return colors.errorStackInternal(`${figures.pointerSmall} ${line}`);
}
return colors.errorStack(`${figures.pointerSmall} ${line}`);
});
}
return [error.stack];
}
writeLogs(event, surroundLines) {
if (event.logs && event.logs.length > 0) {
if (surroundLines) {
this.lineWriter.writeLine();
}
for (const log of event.logs) {
const logLines = indentString(colors.log(log), 4);
const logLinesWithLeadingFigure = logLines.replace(/^ {4}/, ` ${colors.information(figures.info)} `);
this.lineWriter.writeLine(logLinesWithLeadingFigure);
}
if (surroundLines) {
this.lineWriter.writeLine();
}
return true;
}
return false;
}
writeTestSummary(event) {
// Prefix icon indicates matched expectations vs. not.
// Prefix color indicates passed-as-expected vs. not (fail or unexpected pass).
// This yields four possibilities, which in the standard configuration render as:
// * normal test, pass: <green>✔</green>
// * normal test, fail: <red>✘ [fail]</red>
// * fail-expected test, fail: <red>✔ [expected fail]</red>
// * fail-expected test, pass: <red>✘ [unexpected pass]</red>
let prefix;
let suffix;
if (event.type === 'hook-failed' || event.type === 'test-failed') {
const type = event.knownFailing ? '[unexpected pass]' : '[fail]';
prefix = colors.error(`${figures.cross} ${type}:`);
suffix = chalk.italic(colors.error(event.err.message));
} else if (event.knownFailing) {
prefix = colors.error(figures.tick + ' [expected fail]');
} else {
prefix = colors.pass(figures.tick);
if (event.duration > this.durationThreshold) {
suffix = colors.duration(`(${prettyMs(event.duration)})`);
}
}
const label = this.prefixTitle(event.testFile, event.title);
this.write(`${prefix} ${label}${suffix ? ' ' + suffix : ''}`);
this.writeLogs(event);
}
writeFailure(event) {
this.lineWriter.writeLine(colors.title(this.prefixTitle(event.testFile, event.title)));
if (!event.logs || event.logs.length === 0) {
this.lineWriter.writeLine();
}
this.writeErr(event);
}
endRun() {// eslint-disable-line complexity
let firstLinePostfix = this.watching ? ` ${chalk.gray.dim(`[${new Date().toLocaleTimeString('en-US', {hour12: false})}]`)}` : '';
if (this.emptyParallelRun) {
this.lineWriter.writeLine('No files tested in this parallel run');
this.lineWriter.writeLine();
return;
}
if (this.selectionInsights.ignoredFilterPatternFiles.length > 0) {
this.write(colors.information(`${figures.warning} Paths for additional test files were disregarded:`));
this.lineWriter.writeLine();
for (const pattern of this.selectionInsights.ignoredFilterPatternFiles) {
this.lineWriter.writeLine(chalk.magenta(`* ${pattern}`));
}
this.lineWriter.writeLine();
this.write(colors.information('Files starting with underscores are never treated as test files.'));
this.write(colors.information('Files handled by @ava/typescript can only be selected if your configuration already selects them.'));
this.lineWriter.writeLine();
}
if (this.selectionInsights.selectionCount === 0) {
if (this.selectionInsights.testFileCount === 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldnt find any files to test` + firstLinePostfix));
} else {
const {testFileCount: count} = this.selectionInsights;
this.lineWriter.writeLine(colors.error(`${figures.cross} Based on your configuration, ${count} test ${plur('file was', 'files were', count)} found, but did not match the CLI arguments:` + firstLinePostfix));
this.lineWriter.writeLine();
for (const {pattern} of this.selectionInsights.filter) {
this.lineWriter.writeLine(colors.error(`* ${pattern}`));
}
}
this.lineWriter.writeLine();
return;
}
if (this.matching && this.stats.selectedTests === 0) {
this.lineWriter.writeLine(colors.error(`${figures.cross} Couldnt find any matching tests` + firstLinePostfix));
this.lineWriter.writeLine();
return;
}
this.lineWriter.writeLine(colors.log(figures.line));
this.lineWriter.writeLine();
if (this.failures.length > 0) {
const lastFailure = this.failures[this.failures.length - 1];
for (const event of this.failures) {
this.writeFailure(event);
if (event !== lastFailure) {
this.lineWriter.writeLine();
this.lineWriter.writeLine();
}
}
this.lineWriter.writeLine(colors.log(figures.line));
this.lineWriter.writeLine();
}
if (this.failFastEnabled && (this.stats.remainingTests > 0 || this.stats.files > this.stats.finishedWorkers)) {
let remaining = '';
if (this.stats.remainingTests > 0) {
remaining += `At least ${this.stats.remainingTests} ${plur('test was', 'tests were', this.stats.remainingTests)} skipped`;
if (this.stats.files > this.stats.finishedWorkers) {
remaining += ', as well as ';
}
}
if (this.stats.files > this.stats.finishedWorkers) {
const skippedFileCount = this.stats.files - this.stats.finishedWorkers;
remaining += `${skippedFileCount} ${plur('test file', 'test files', skippedFileCount)}`;
if (this.stats.remainingTests === 0) {
remaining += ` ${plur('was', 'were', skippedFileCount)} skipped`;
}
}
this.lineWriter.writeLine(colors.information(`\`--fail-fast\` is on. ${remaining}.`));
this.lineWriter.writeLine();
}
if (this.stats.parallelRuns) {
const {
currentFileCount,
currentIndex,
totalRuns,
} = this.stats.parallelRuns;
this.lineWriter.writeLine(colors.information(`Ran ${currentFileCount} test ${plur('file', currentFileCount)} out of ${this.stats.files} for job ${currentIndex + 1} of ${totalRuns}`));
this.lineWriter.writeLine();
}
if (this.stats.failedHooks > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (this.stats.failedTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix);
firstLinePostfix = '';
}
if (
this.stats.failedHooks === 0
&& this.stats.failedTests === 0
&& this.stats.passedTests > 0
) {
this.lineWriter.writeLine(colors.pass(`${this.stats.passedTests} ${plur('test', this.stats.passedTests)} passed`) + firstLinePostfix,
);
firstLinePostfix = '';
}
if (this.stats.passedKnownFailingTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`));
}
if (this.stats.skippedTests > 0) {
this.lineWriter.writeLine(colors.skip(`${this.stats.skippedTests} ${plur('test', this.stats.skippedTests)} skipped`));
}
if (this.stats.todoTests > 0) {
this.lineWriter.writeLine(colors.todo(`${this.stats.todoTests} ${plur('test', this.stats.todoTests)} todo`));
}
if (this.stats.unhandledRejections > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.unhandledRejections} unhandled ${plur('rejection', this.stats.unhandledRejections)}`));
}
if (this.stats.uncaughtExceptions > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.uncaughtExceptions} uncaught ${plur('exception', this.stats.uncaughtExceptions)}`));
}
if (this.stats.timedOutTests > 0) {
this.lineWriter.writeLine(colors.error(`${this.stats.timedOutTests} ${plur('test', this.stats.timedOutTests)} remained pending after a timeout`));
}
if (this.previousFailures > 0) {
this.lineWriter.writeLine(colors.error(`${this.previousFailures} previous ${plur('failure', this.previousFailures)} in test files that were not rerun`));
}
if (this.watching) {
this.lineWriter.writeLine();
}
}
}

View File

@@ -0,0 +1,16 @@
export default function formatSerializedError(error) {
const printMessage = error.values.length === 0
? Boolean(error.message)
: !error.values[0].label.startsWith(error.message);
if (error.values.length === 0) {
return {formatted: null, printMessage};
}
let formatted = '';
for (const value of error.values) {
formatted += `${value.label}\n\n${value.formatted}\n\n`;
}
return {formatted: formatted.trim(), printMessage};
}

View File

@@ -0,0 +1,54 @@
import {chalk} from '../chalk.js';
import pkg from '../pkg.cjs';
export default function buildMessage(error) {
if (!error.improperUsage) {
return null;
}
const {assertion} = error;
if (assertion === 'throws' || assertion === 'notThrows') {
return `Try wrapping the first argument to \`t.${assertion}()\` in a function:
${chalk.cyan(`t.${assertion}(() => { `)}${chalk.grey('/* your code here */')}${chalk.cyan(' })')}
Visit the following URL for more details:
${chalk.blue.underline(`https://github.com/avajs/ava/blob/v${pkg.version}/docs/03-assertions.md#throwsfn-expected-message`)}`;
}
if (assertion === 'snapshot') {
const {name, snapPath} = error.improperUsage;
if (name === 'ChecksumError' || name === 'InvalidSnapshotError') {
return `The snapshot file is corrupted.
File path: ${chalk.yellow(snapPath)}
Please run AVA again with the ${chalk.cyan('--update-snapshots')} flag to recreate it.`;
}
if (name === 'LegacyError') {
return `The snapshot file was created with AVA 0.19. Its not supported by this AVA version.
File path: ${chalk.yellow(snapPath)}
Please run AVA again with the ${chalk.cyan('--update-snapshots')} flag to upgrade.`;
}
if (name === 'VersionMismatchError') {
const {snapVersion, expectedVersion} = error.improperUsage;
const upgradeMessage = snapVersion < expectedVersion
? `Please run AVA again with the ${chalk.cyan('--update-snapshots')} flag to upgrade.`
: 'You should upgrade AVA.';
return `The snapshot file is v${snapVersion}, but only v${expectedVersion} is supported.
File path: ${chalk.yellow(snapPath)}
${upgradeMessage}`;
}
}
return null;
}

View File

@@ -0,0 +1,23 @@
import path from 'node:path';
import figures from 'figures';
import {chalk} from '../chalk.js';
const SEPARATOR = ' ' + chalk.gray.dim(figures.pointerSmall) + ' ';
export default function prefixTitle(extensions, base, file, title) {
const parts = file
// Only replace base if it is found at the start of the path
.replace(base, (match, offset) => offset === 0 ? '' : match)
.split(path.sep)
.filter(p => p !== '__tests__');
const filename = parts.pop()
.replace(/\.spec\./, '.')
.replace(/\.test\./, '.')
.replace(/test-/, '')
.replace(new RegExp(`.(${extensions.join('|')})$`), '');
return [...parts, filename, title].join(SEPARATOR);
}

View File

@@ -0,0 +1,262 @@
import os from 'node:os';
import path from 'node:path';
import indentString from 'indent-string';
import plur from 'plur';
import stripAnsi from 'strip-ansi';
import * as supertap from 'supertap';
import beautifyStack from './beautify-stack.js';
import prefixTitle from './prefix-title.js';
function dumpError(error) {
const object = {...error.object};
if (error.name) {
object.name = error.name;
}
if (error.message) {
object.message = error.message;
}
if (error.avaAssertionError) {
if (error.assertion) {
object.assertion = error.assertion;
}
if (error.operator) {
object.operator = error.operator;
}
if (error.values.length > 0) {
object.values = Object.fromEntries(error.values.map(({label, formatted}) => [stripAnsi(label), stripAnsi(formatted)]));
}
}
if (error.nonErrorObject) {
object.message = 'Non-error object';
object.formatted = stripAnsi(error.formatted);
}
if (error.stack) {
object.at = error.shouldBeautifyStack ? beautifyStack(error.stack).join('\n') : error.stack;
}
return object;
}
export default class TapReporter {
constructor(options) {
this.i = 0;
this.extensions = options.extensions;
this.stdStream = options.stdStream;
this.reportStream = options.reportStream;
this.crashCount = 0;
this.filesWithMissingAvaImports = new Set();
this.prefixTitle = (testFile, title) => title;
this.relativeFile = file => path.relative(options.projectDir, file);
this.stats = null;
}
startRun(plan) {
if (plan.files.length > 1) {
this.prefixTitle = (testFile, title) => prefixTitle(this.extensions, plan.filePathPrefix, testFile, title);
}
plan.status.on('stateChange', evt => this.consumeStateChange(evt));
this.reportStream.write(supertap.start() + os.EOL);
}
endRun() {
if (this.stats) {
this.reportStream.write(supertap.finish({
crashed: this.crashCount,
failed: this.stats.failedTests + this.stats.remainingTests,
passed: this.stats.passedTests + this.stats.passedKnownFailingTests,
skipped: this.stats.skippedTests,
todo: this.stats.todoTests,
}) + os.EOL);
if (this.stats.parallelRuns) {
const {currentFileCount, currentIndex, totalRuns} = this.stats.parallelRuns;
this.reportStream.write(`# Ran ${currentFileCount} test ${plur('file', currentFileCount)} out of ${this.stats.files} for job ${currentIndex + 1} of ${totalRuns}` + os.EOL + os.EOL);
}
} else {
this.reportStream.write(supertap.finish({
crashed: this.crashCount,
failed: 0,
passed: 0,
skipped: 0,
todo: 0,
}) + os.EOL);
}
}
writeTest(evt, flags) {
this.reportStream.write(supertap.test(this.prefixTitle(evt.testFile, evt.title), {
comment: evt.logs,
error: evt.err ? dumpError(evt.err) : null,
index: ++this.i,
passed: flags.passed,
skip: flags.skip,
todo: flags.todo,
}) + os.EOL);
}
writeCrash(evt, title) {
this.crashCount++;
this.reportStream.write(supertap.test(title || evt.err.summary || evt.type, {
comment: evt.logs,
error: evt.err ? dumpError(evt.err) : null,
index: ++this.i,
passed: false,
skip: false,
todo: false,
}) + os.EOL);
}
writeComment(evt, {title = this.prefixTitle(evt.testFile, evt.title)}) {
this.reportStream.write(`# ${stripAnsi(title)}${os.EOL}`);
if (evt.logs) {
for (const log of evt.logs) {
const logLines = indentString(log, 4).replace(/^ {4}/gm, '# ');
this.reportStream.write(`${logLines}${os.EOL}`);
}
}
}
writeProcessExit(evt) {
const error = new Error(`Exiting due to process.exit() when running ${this.relativeFile(evt.testFile)}`);
error.stack = evt.stack;
for (const [testFile, tests] of evt.pendingTests) {
for (const title of tests) {
this.writeTest({testFile, title, err: error}, {passed: false, todo: false, skip: false});
}
}
}
writeTimeout(evt) {
const error = new Error(`Exited because no new tests completed within the last ${evt.period}ms of inactivity`);
for (const [testFile, tests] of evt.pendingTests) {
for (const title of tests) {
this.writeTest({testFile, title, err: error}, {passed: false, todo: false, skip: false});
}
}
}
consumeStateChange(evt) { // eslint-disable-line complexity
const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null;
switch (evt.type) {
case 'declared-test': {
// Ignore
break;
}
case 'hook-failed': {
this.writeTest(evt, {passed: false, todo: false, skip: false});
break;
}
case 'hook-finished': {
this.writeComment(evt, {});
break;
}
case 'internal-error': {
this.writeCrash(evt);
break;
}
case 'missing-ava-import': {
this.filesWithMissingAvaImports.add(evt.testFile);
this.writeCrash(evt, `No tests found in ${this.relativeFile(evt.testFile)}, make sure to import "ava" at the top of your test file`);
break;
}
case 'process-exit': {
this.writeProcessExit(evt);
break;
}
case 'selected-test': {
if (evt.skip) {
this.writeTest(evt, {passed: true, todo: false, skip: true});
} else if (evt.todo) {
this.writeTest(evt, {passed: false, todo: true, skip: false});
}
break;
}
case 'stats': {
this.stats = evt.stats;
break;
}
case 'test-failed': {
this.writeTest(evt, {passed: false, todo: false, skip: false});
break;
}
case 'test-passed': {
this.writeTest(evt, {passed: true, todo: false, skip: false});
break;
}
case 'timeout': {
this.writeTimeout(evt);
break;
}
case 'uncaught-exception': {
this.writeCrash(evt);
break;
}
case 'unhandled-rejection': {
this.writeCrash(evt);
break;
}
case 'worker-failed': {
if (!this.filesWithMissingAvaImports.has(evt.testFile)) {
if (evt.nonZeroExitCode) {
this.writeCrash(evt, `${this.relativeFile(evt.testFile)} exited with a non-zero exit code: ${evt.nonZeroExitCode}`);
} else {
this.writeCrash(evt, `${this.relativeFile(evt.testFile)} exited due to ${evt.signal}`);
}
}
break;
}
case 'worker-finished': {
if (!evt.forcedExit && !this.filesWithMissingAvaImports.has(evt.testFile)) {
if (fileStats.declaredTests === 0) {
this.writeCrash(evt, `No tests found in ${this.relativeFile(evt.testFile)}`);
} else if (!this.failFastEnabled && fileStats.remainingTests > 0) {
this.writeComment(evt, {title: `${fileStats.remainingTests} ${plur('test', fileStats.remainingTests)} remaining in ${this.relativeFile(evt.testFile)}`});
}
}
break;
}
case 'worker-stderr':
case 'worker-stdout': {
this.stdStream.write(evt.chunk);
break;
}
default: {
break;
}
}
}
}

View File

@@ -0,0 +1,262 @@
import v8 from 'node:v8';
import Emittery from 'emittery';
const copyStats = stats => v8.deserialize(v8.serialize(stats));
export default class RunStatus extends Emittery {
constructor(files, parallelRuns, selectionInsights) {
super();
this.pendingTests = new Map();
this.pendingTestsLogs = new Map();
this.emptyParallelRun = parallelRuns
&& parallelRuns.currentFileCount === 0
&& parallelRuns.totalRuns > 1
&& files > 0;
this.selectionInsights = selectionInsights;
this.stats = {
byFile: new Map(),
declaredTests: 0,
failedHooks: 0,
failedTests: 0,
failedWorkers: 0,
files,
parallelRuns,
finishedWorkers: 0,
internalErrors: 0,
remainingTests: 0,
passedKnownFailingTests: 0,
passedTests: 0,
selectedTests: 0,
sharedWorkerErrors: 0,
skippedTests: 0,
timedOutTests: 0,
timeouts: 0,
todoTests: 0,
uncaughtExceptions: 0,
unhandledRejections: 0,
};
}
observeWorker(worker, testFile, stats) {
this.stats.byFile.set(testFile, {
declaredTests: 0,
failedHooks: 0,
failedTests: 0,
internalErrors: 0,
remainingTests: 0,
passedKnownFailingTests: 0,
passedTests: 0,
selectedTests: 0,
selectingLines: false,
skippedTests: 0,
todoTests: 0,
uncaughtExceptions: 0,
unhandledRejections: 0,
...stats,
});
this.pendingTests.set(testFile, new Set());
this.pendingTestsLogs.set(testFile, new Map());
worker.onStateChange(data => this.emitStateChange(data));
}
// eslint-disable-next-line complexity
emitStateChange(event) {
const {stats} = this;
const fileStats = stats.byFile.get(event.testFile);
let changedStats = true;
switch (event.type) {
case 'declared-test': {
stats.declaredTests++;
fileStats.declaredTests++;
break;
}
case 'hook-failed': {
stats.failedHooks++;
fileStats.failedHooks++;
break;
}
case 'internal-error': {
stats.internalErrors++;
if (event.testFile) {
fileStats.internalErrors++;
}
break;
}
case 'selected-test': {
stats.selectedTests++;
fileStats.selectedTests++;
if (event.skip) {
stats.skippedTests++;
fileStats.skippedTests++;
} else if (event.todo) {
stats.todoTests++;
fileStats.todoTests++;
} else {
stats.remainingTests++;
fileStats.remainingTests++;
this.addPendingTest(event);
}
break;
}
case 'shared-worker-error': {
stats.sharedWorkerErrors++;
break;
}
case 'test-failed': {
stats.failedTests++;
fileStats.failedTests++;
stats.remainingTests--;
fileStats.remainingTests--;
this.removePendingTest(event);
break;
}
case 'test-passed': {
if (event.knownFailing) {
stats.passedKnownFailingTests++;
fileStats.passedKnownFailingTests++;
} else {
stats.passedTests++;
fileStats.passedTests++;
}
stats.remainingTests--;
fileStats.remainingTests--;
this.removePendingTest(event);
break;
}
case 'test-register-log-reference': {
this.addPendingTestLogs(event);
break;
}
case 'timeout': {
stats.timeouts++;
event.pendingTests = this.pendingTests;
event.pendingTestsLogs = this.pendingTestsLogs;
this.pendingTests = new Map();
this.pendingTestsLogs = new Map();
for (const testsInFile of event.pendingTests.values()) {
stats.timedOutTests += testsInFile.size;
}
break;
}
case 'interrupt': {
event.pendingTests = this.pendingTests;
event.pendingTestsLogs = this.pendingTestsLogs;
this.pendingTests = new Map();
this.pendingTestsLogs = new Map();
break;
}
case 'process-exit': {
event.pendingTests = this.pendingTests;
event.pendingTestsLogs = this.pendingTestsLogs;
this.pendingTests = new Map();
this.pendingTestsLogs = new Map();
break;
}
case 'uncaught-exception': {
stats.uncaughtExceptions++;
fileStats.uncaughtExceptions++;
break;
}
case 'unhandled-rejection': {
stats.unhandledRejections++;
fileStats.unhandledRejections++;
break;
}
case 'worker-failed': {
stats.failedWorkers++;
break;
}
case 'worker-finished': {
stats.finishedWorkers++;
break;
}
default: {
changedStats = false;
break;
}
}
if (changedStats) {
this.emit('stateChange', {type: 'stats', stats: copyStats(stats)});
}
this.emit('stateChange', event);
}
suggestExitCode(circumstances) {
if (this.emptyParallelRun) {
return 0;
}
if (circumstances.matching && this.stats.selectedTests === 0) {
return 1;
}
if (
this.stats.declaredTests === 0
|| this.stats.internalErrors > 0
|| this.stats.failedHooks > 0
|| this.stats.failedTests > 0
|| this.stats.failedWorkers > 0
|| this.stats.remainingTests > 0
|| this.stats.sharedWorkerErrors > 0
|| this.stats.timeouts > 0
|| this.stats.uncaughtExceptions > 0
|| this.stats.unhandledRejections > 0
) {
return 1;
}
if ([...this.stats.byFile.values()].some(stats => stats.selectingLines && stats.selectedTests === 0)) {
return 1;
}
return 0;
}
addPendingTestLogs(event) {
this.pendingTestsLogs.get(event.testFile)?.set(event.title, event.logs);
}
addPendingTest(event) {
if (this.pendingTests.has(event.testFile)) {
this.pendingTests.get(event.testFile).add(event.title);
}
}
removePendingTest(event) {
if (this.pendingTests.has(event.testFile)) {
this.pendingTests.get(event.testFile).delete(event.title);
}
}
getFailedTestFiles() {
return [...this.stats.byFile].filter(statByFile => statByFile[1].failedTests).map(statByFile => statByFile[0]);
}
}

558
github/codeql-action-v2/node_modules/ava/lib/runner.js generated vendored Normal file
View File

@@ -0,0 +1,558 @@
import process from 'node:process';
import {pathToFileURL} from 'node:url';
import Emittery from 'emittery';
import {matcher} from 'matcher';
import ContextRef from './context-ref.js';
import createChain from './create-chain.js';
import parseTestArgs from './parse-test-args.js';
import serializeError from './serialize-error.js';
import {load as loadSnapshots, determineSnapshotDir} from './snapshot-manager.js';
import Runnable from './test.js';
import {waitForReady} from './worker/state.cjs';
const makeFileURL = file => file.startsWith('file://') ? file : pathToFileURL(file).toString();
export default class Runner extends Emittery {
constructor(options = {}) {
super();
this.experiments = options.experiments || {};
this.failFast = options.failFast === true;
this.failWithoutAssertions = options.failWithoutAssertions !== false;
this.file = options.file;
this.checkSelectedByLineNumbers = options.checkSelectedByLineNumbers;
this.match = options.match || [];
this.projectDir = options.projectDir;
this.recordNewSnapshots = options.recordNewSnapshots === true;
this.runOnlyExclusive = options.runOnlyExclusive === true;
this.serial = options.serial === true;
this.snapshotDir = options.snapshotDir;
this.updateSnapshots = options.updateSnapshots;
this.activeRunnables = new Set();
this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
this.boundSkipSnapshot = this.skipSnapshot.bind(this);
this.interrupted = false;
this.nextTaskIndex = 0;
this.tasks = {
after: [],
afterAlways: [],
afterEach: [],
afterEachAlways: [],
before: [],
beforeEach: [],
concurrent: [],
serial: [],
todo: [],
};
this.waitForReady = waitForReady;
const uniqueTestTitles = new Set();
this.registerUniqueTitle = title => {
if (uniqueTestTitles.has(title)) {
return false;
}
uniqueTestTitles.add(title);
return true;
};
this.notifyTimeoutUpdate = timeoutMs => {
this.emit('stateChange', {
type: 'test-timeout-configured',
period: timeoutMs,
});
};
let hasStarted = false;
let scheduledStart = false;
const meta = Object.freeze({
file: makeFileURL(options.file),
get snapshotDirectory() {
const {file, snapshotDir: fixedLocation, projectDir} = options;
return makeFileURL(determineSnapshotDir({file, fixedLocation, projectDir}));
},
});
this.chain = createChain((metadata, testArgs) => { // eslint-disable-line complexity
if (hasStarted) {
throw new Error('All tests and hooks must be declared synchronously in your test file, and cannot be nested within other tests or hooks.');
}
if (!scheduledStart) {
scheduledStart = true;
process.nextTick(() => {
hasStarted = true;
this.start();
});
}
metadata.taskIndex = this.nextTaskIndex++;
const {args, implementation, title} = parseTestArgs(testArgs);
if (this.checkSelectedByLineNumbers) {
metadata.selected = this.checkSelectedByLineNumbers();
}
if (metadata.todo) {
if (implementation) {
throw new TypeError('`todo` tests are not allowed to have an implementation. Use `test.skip()` for tests with an implementation.');
}
if (!title.raw) { // Either undefined or a string.
throw new TypeError('`todo` tests require a title');
}
if (!this.registerUniqueTitle(title.value)) {
throw new Error(`Duplicate test title: ${title.value}`);
}
// --match selects TODO tests.
if (this.match.length > 0 && matcher(title.value, this.match).length === 1) {
metadata.exclusive = true;
this.runOnlyExclusive = true;
}
this.tasks.todo.push({title: title.value, metadata});
this.emit('stateChange', {
type: 'declared-test',
title: title.value,
knownFailing: false,
todo: true,
});
} else {
if (!implementation) {
throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.');
}
if (Array.isArray(implementation)) {
throw new TypeError('AVA 4 no longer supports multiple implementations.');
}
if (title.isSet && !title.isValid) {
throw new TypeError('Test & hook titles must be strings');
}
let fallbackTitle = title.value;
if (title.isEmpty) {
if (metadata.type === 'test') {
throw new TypeError('Tests must have a title');
} else if (metadata.always) {
fallbackTitle = `${metadata.type}.always hook`;
} else {
fallbackTitle = `${metadata.type} hook`;
}
}
if (metadata.type === 'test' && !this.registerUniqueTitle(title.value)) {
throw new Error(`Duplicate test title: ${title.value}`);
}
const task = {
title: title.value || fallbackTitle,
implementation,
args,
metadata: {...metadata},
};
if (metadata.type === 'test') {
if (this.match.length > 0) {
// --match overrides .only()
task.metadata.exclusive = matcher(title.value, this.match).length === 1;
}
if (task.metadata.exclusive) {
this.runOnlyExclusive = true;
}
this.tasks[metadata.serial ? 'serial' : 'concurrent'].push(task);
this.snapshots.touch(title.value, metadata.taskIndex);
this.emit('stateChange', {
type: 'declared-test',
title: title.value,
knownFailing: metadata.failing,
todo: false,
});
} else if (!metadata.skipped) {
this.tasks[metadata.type + (metadata.always ? 'Always' : '')].push(task);
}
}
}, {
serial: false,
exclusive: false,
skipped: false,
todo: false,
failing: false,
callback: false,
inline: false, // Set for attempt metadata created by `t.try()`
always: false,
}, meta);
}
get snapshots() {
if (this._snapshots) {
return this._snapshots;
}
// Lazy load not when the runner is instantiated but when snapshots are
// needed. This should be after the test file has been loaded and source
// maps are available.
const snapshots = loadSnapshots({
file: this.file,
fixedLocation: this.snapshotDir,
projectDir: this.projectDir,
recordNewSnapshots: this.recordNewSnapshots,
updating: this.updateSnapshots,
});
if (snapshots.snapPath !== undefined) {
this.emit('dependency', snapshots.snapPath);
}
this._snapshots = snapshots;
return snapshots;
}
compareTestSnapshot(options) {
return this.snapshots.compare(options);
}
skipSnapshot(options) {
return this.snapshots.skipSnapshot(options);
}
async saveSnapshotState() {
return {touchedFiles: await this.snapshots.save()};
}
onRun(runnable) {
this.activeRunnables.add(runnable);
}
onRunComplete(runnable) {
this.activeRunnables.delete(runnable);
}
beforeExitHandler() {
for (const runnable of this.activeRunnables) {
runnable.finishDueToInactivity();
}
}
async runMultiple(runnables) {
let allPassed = true;
const storedResults = [];
const runAndStoreResult = async runnable => {
const result = await this.runSingle(runnable);
if (!result.passed) {
allPassed = false;
}
storedResults.push(result);
};
let waitForSerial = Promise.resolve();
await runnables.reduce((previous, runnable) => { // eslint-disable-line unicorn/no-array-reduce
if (runnable.metadata.serial || this.serial) {
waitForSerial = previous.then(() =>
// Serial runnables run as long as there was no previous failure, unless
// the runnable should always be run.
(allPassed || runnable.metadata.always) && runAndStoreResult(runnable),
);
return waitForSerial;
}
return Promise.all([
previous,
waitForSerial.then(() =>
// Concurrent runnables are kicked off after the previous serial
// runnables have completed, as long as there was no previous failure
// (or if the runnable should always be run). One concurrent runnable's
// failure does not prevent the next runnable from running.
(allPassed || runnable.metadata.always) && runAndStoreResult(runnable),
),
]);
}, waitForSerial);
return {allPassed, storedResults};
}
async runSingle(runnable) {
this.onRun(runnable);
const result = await runnable.run();
// If run() throws or rejects then the entire test run crashes, so
// onRunComplete() doesn't *have* to be inside a finally.
this.onRunComplete(runnable);
return result;
}
async runHooks(tasks, contextRef, {titleSuffix, testPassed} = {}) {
const hooks = tasks.map(task => new Runnable({
contextRef,
experiments: this.experiments,
failWithoutAssertions: false,
fn: task.args.length === 0
? task.implementation
: t => Reflect.apply(task.implementation, null, [t, ...task.args]),
compareTestSnapshot: this.boundCompareTestSnapshot,
skipSnapshot: this.boundSkipSnapshot,
updateSnapshots: this.updateSnapshots,
metadata: task.metadata,
title: `${task.title}${titleSuffix || ''}`,
isHook: true,
testPassed,
notifyTimeoutUpdate: this.notifyTimeoutUpdate,
}));
const outcome = await this.runMultiple(hooks, this.serial);
for (const result of outcome.storedResults) {
if (result.passed) {
this.emit('stateChange', {
type: 'hook-finished',
title: result.title,
duration: result.duration,
logs: result.logs,
});
} else {
this.emit('stateChange', {
type: 'hook-failed',
title: result.title,
err: serializeError('Hook failure', true, result.error),
duration: result.duration,
logs: result.logs,
});
}
}
return outcome.allPassed;
}
async runTest(task, contextRef) {
const hookSuffix = ` for ${task.title}`;
let hooksOk = await this.runHooks(
this.tasks.beforeEach,
contextRef,
{
titleSuffix: hookSuffix,
},
);
let testOk = false;
if (hooksOk) {
// Only run the test if all `beforeEach` hooks passed.
const test = new Runnable({
contextRef,
experiments: this.experiments,
failWithoutAssertions: this.failWithoutAssertions,
fn: task.args.length === 0
? task.implementation
: t => Reflect.apply(task.implementation, null, [t, ...task.args]),
compareTestSnapshot: this.boundCompareTestSnapshot,
skipSnapshot: this.boundSkipSnapshot,
updateSnapshots: this.updateSnapshots,
metadata: task.metadata,
title: task.title,
registerUniqueTitle: this.registerUniqueTitle,
notifyTimeoutUpdate: this.notifyTimeoutUpdate,
});
this.emit('stateChange', {
type: 'test-register-log-reference',
title: task.title,
logs: test.logs,
});
const result = await this.runSingle(test);
testOk = result.passed;
if (testOk) {
this.emit('stateChange', {
type: 'test-passed',
title: result.title,
duration: result.duration,
knownFailing: result.metadata.failing,
logs: result.logs,
});
hooksOk = await this.runHooks(
this.tasks.afterEach,
contextRef,
{
titleSuffix: hookSuffix,
testPassed: testOk,
});
} else {
this.emit('stateChange', {
type: 'test-failed',
title: result.title,
err: serializeError('Test failure', true, result.error, this.file),
duration: result.duration,
knownFailing: result.metadata.failing,
logs: result.logs,
});
// Don't run `afterEach` hooks if the test failed.
}
}
const alwaysOk = await this.runHooks(
this.tasks.afterEachAlways,
contextRef,
{
titleSuffix: hookSuffix,
testPassed: testOk,
});
return alwaysOk && hooksOk && testOk;
}
async start() { // eslint-disable-line complexity
const concurrentTests = [];
const serialTests = [];
for (const task of this.tasks.serial) {
if (this.runOnlyExclusive && !task.metadata.exclusive) {
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
continue;
}
if (this.checkSelectedByLineNumbers && !task.metadata.selected) {
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
continue;
}
this.emit('stateChange', {
type: 'selected-test',
title: task.title,
knownFailing: task.metadata.failing,
skip: task.metadata.skipped,
todo: false,
});
if (task.metadata.skipped) {
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
} else {
serialTests.push(task);
}
}
for (const task of this.tasks.concurrent) {
if (this.runOnlyExclusive && !task.metadata.exclusive) {
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
continue;
}
if (this.checkSelectedByLineNumbers && !task.metadata.selected) {
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
continue;
}
this.emit('stateChange', {
type: 'selected-test',
title: task.title,
knownFailing: task.metadata.failing,
skip: task.metadata.skipped,
todo: false,
});
if (task.metadata.skipped) {
this.snapshots.skipBlock(task.title, task.metadata.taskIndex);
} else if (this.serial) {
serialTests.push(task);
} else {
concurrentTests.push(task);
}
}
for (const task of this.tasks.todo) {
if (this.runOnlyExclusive && !task.metadata.exclusive) {
continue;
}
if (this.checkSelectedByLineNumbers && !task.metadata.selected) {
continue;
}
this.emit('stateChange', {
type: 'selected-test',
title: task.title,
knownFailing: false,
skip: false,
todo: true,
});
}
await Promise.all(this.waitForReady);
if (concurrentTests.length === 0 && serialTests.length === 0) {
this.emit('finish');
// Don't run any hooks if there are no tests to run.
return;
}
const contextRef = new ContextRef();
// Note that the hooks and tests always begin running asynchronously.
const beforePromise = this.runHooks(this.tasks.before, contextRef);
const serialPromise = beforePromise.then(beforeHooksOk => {
// Don't run tests if a `before` hook failed.
if (!beforeHooksOk) {
return false;
}
return serialTests.reduce(async (previous, task) => { // eslint-disable-line unicorn/no-array-reduce
const previousOk = await previous;
// Don't start tests after an interrupt.
if (this.interrupted) {
return previousOk;
}
// Prevent subsequent tests from running if `failFast` is enabled and
// the previous test failed.
if (!previousOk && this.failFast) {
return false;
}
return this.runTest(task, contextRef.copy());
}, true);
});
const concurrentPromise = Promise.all([beforePromise, serialPromise]).then(async ([beforeHooksOk, serialOk]) => {
// Don't run tests if a `before` hook failed, or if `failFast` is enabled
// and a previous serial test failed.
if (!beforeHooksOk || (!serialOk && this.failFast)) {
return false;
}
// Don't start tests after an interrupt.
if (this.interrupted) {
return true;
}
// If a concurrent test fails, even if `failFast` is enabled it won't
// stop other concurrent tests from running.
const allOkays = await Promise.all(concurrentTests.map(task => this.runTest(task, contextRef.copy())));
return allOkays.every(Boolean);
});
const beforeExitHandler = this.beforeExitHandler.bind(this);
process.on('beforeExit', beforeExitHandler);
try {
const ok = await concurrentPromise;
// Only run `after` hooks if all hooks and tests passed.
if (ok) {
await this.runHooks(this.tasks.after, contextRef);
}
// Always run `after.always` hooks.
await this.runHooks(this.tasks.afterAlways, contextRef);
process.removeListener('beforeExit', beforeExitHandler);
await this.emit('finish');
} catch (error) {
await this.emit('error', error);
}
}
interrupt() {
this.interrupted = true;
}
}

View File

@@ -0,0 +1,53 @@
import fs from 'node:fs';
import path from 'node:path';
import writeFileAtomic from 'write-file-atomic';
import isCi from './is-ci.js';
const FILENAME = 'failing-tests.json';
const scheduler = {
storeFailedTestFiles(runStatus, cacheDir) {
if (isCi || !cacheDir) {
return;
}
try {
writeFileAtomic.sync(path.join(cacheDir, FILENAME), JSON.stringify(runStatus.getFailedTestFiles()));
} catch {}
},
// Order test-files, so that files with failing tests come first
failingTestsFirst(selectedFiles, cacheDir, cacheEnabled) {
if (isCi || cacheEnabled === false) {
return selectedFiles;
}
const filePath = path.join(cacheDir, FILENAME);
let failedTestFiles;
try {
failedTestFiles = JSON.parse(fs.readFileSync(filePath));
} catch {
return selectedFiles;
}
return [...selectedFiles].sort((f, s) => {
if (failedTestFiles.includes(f) && failedTestFiles.includes(s)) {
return 0;
}
if (failedTestFiles.includes(f)) {
return -1;
}
if (failedTestFiles.includes(s)) {
return 1;
}
return 0;
});
},
};
export default scheduler;

View File

@@ -0,0 +1,170 @@
import path from 'node:path';
import process from 'node:process';
import {fileURLToPath, pathToFileURL} from 'node:url';
import cleanYamlObject from 'clean-yaml-object';
import concordance from 'concordance';
import isError from 'is-error';
import StackUtils from 'stack-utils';
import {AssertionError} from './assert.js';
import concordanceOptions from './concordance-options.js';
function isAvaAssertionError(source) {
return source instanceof AssertionError;
}
function filter(propertyName, isRoot) {
return !isRoot || (propertyName !== 'message' && propertyName !== 'name' && propertyName !== 'stack');
}
function normalizeFile(file, ...base) {
return file.startsWith('file://') ? file : pathToFileURL(path.resolve(...base, file)).toString();
}
const stackUtils = new StackUtils();
function extractSource(stack, testFile) {
if (!stack || !testFile) {
return null;
}
testFile = normalizeFile(testFile);
for (const line of stack.split('\n')) {
const callSite = stackUtils.parseLine(line);
if (callSite && normalizeFile(callSite.file) === testFile) {
return {
isDependency: false,
isWithinProject: true,
file: testFile,
line: callSite.line,
};
}
}
return null;
}
function buildSource(source) {
if (!source) {
return null;
}
// Assume the CWD is the project directory. This holds since this function
// is only called in test workers, which are created with their working
// directory set to the project directory.
const projectDir = process.cwd();
const file = normalizeFile(source.file.trim(), projectDir);
const rel = path.relative(projectDir, fileURLToPath(file));
const [segment] = rel.split(path.sep);
const isWithinProject = segment !== '..' && (process.platform !== 'win32' || !segment.includes(':'));
const isDependency = isWithinProject && path.dirname(rel).split(path.sep).includes('node_modules');
return {
isDependency,
isWithinProject,
file,
line: source.line,
};
}
function trySerializeError(error, shouldBeautifyStack, testFile) {
const stack = error.savedError ? error.savedError.stack : error.stack;
const retval = {
avaAssertionError: isAvaAssertionError(error),
nonErrorObject: false,
source: extractSource(stack, testFile),
stack,
shouldBeautifyStack,
};
if (error.actualStack) {
retval.stack = error.actualStack;
}
if (retval.avaAssertionError) {
retval.improperUsage = error.improperUsage;
retval.message = error.message;
retval.name = error.name;
retval.values = error.values;
if (error.fixedSource) {
const source = buildSource(error.fixedSource);
if (source) {
retval.source = source;
}
}
if (error.assertion) {
retval.assertion = error.assertion;
}
if (error.operator) {
retval.operator = error.operator;
}
} else {
retval.object = cleanYamlObject(error, filter); // Cleanly copy non-standard properties
if (typeof error.message === 'string') {
retval.message = error.message;
}
if (typeof error.name === 'string') {
retval.name = error.name;
}
}
if (typeof error.stack === 'string') {
const lines = error.stack.split('\n');
if (error.name === 'SyntaxError' && !lines[0].startsWith('SyntaxError')) {
retval.summary = '';
for (const line of lines) {
retval.summary += line + '\n';
if (line.startsWith('SyntaxError')) {
break;
}
}
retval.summary = retval.summary.trim();
} else {
retval.summary = '';
for (let index = 0; index < lines.length; index++) {
if (lines[index].startsWith(' at')) {
break;
}
const next = index + 1;
const end = next === lines.length || lines[next].startsWith(' at');
retval.summary += end ? lines[index] : lines[index] + '\n';
}
}
}
return retval;
}
export default function serializeError(origin, shouldBeautifyStack, error, testFile) {
if (!isError(error)) {
return {
avaAssertionError: false,
nonErrorObject: true,
formatted: concordance.formatDescriptor(concordance.describe(error, concordanceOptions), concordanceOptions),
};
}
try {
return trySerializeError(error, shouldBeautifyStack, testFile);
} catch {
const replacement = new Error(`${origin}: Could not serialize error`);
return {
avaAssertionError: false,
nonErrorObject: false,
name: replacement.name,
message: replacement.message,
stack: replacement.stack,
summary: replacement.message,
};
}
}

36
github/codeql-action-v2/node_modules/ava/lib/slash.cjs generated vendored Normal file
View File

@@ -0,0 +1,36 @@
/*
Inlined from
<https://github.com/sindresorhus/slash/blob/98b618f5a3bfcb5dd374b204868818845b87bb2f/index.js>,
since we need a CJS version.
Copyright 2023 Sindre Sorhus
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.
*/
function slash(path) {
const isExtendedLengthPath = path.startsWith('\\\\?\\');
if (isExtendedLengthPath) {
return path;
}
return path.replace(/\\/g, '/');
}
module.exports = slash;

View File

@@ -0,0 +1,507 @@
import {Buffer} from 'node:buffer';
import crypto from 'node:crypto';
import fs from 'node:fs';
import {findSourceMap} from 'node:module';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import zlib from 'node:zlib';
import cbor from 'cbor';
import concordance from 'concordance';
import indentString from 'indent-string';
import mem from 'mem';
import writeFileAtomic from 'write-file-atomic';
import {snapshotManager as concordanceOptions} from './concordance-options.js';
import slash from './slash.cjs';
// Increment if encoding layout or Concordance serialization versions change. Previous AVA versions will not be able to
// decode buffers generated by a newer version, so changing this value will require a major version bump of AVA itself.
// The version is encoded as an unsigned 16 bit integer.
const VERSION = 3;
const VERSION_HEADER = Buffer.alloc(2);
VERSION_HEADER.writeUInt16LE(VERSION);
// The decoder matches on the trailing newline byte (0x0A).
const READABLE_PREFIX = Buffer.from(`AVA Snapshot v${VERSION}\n`, 'ascii');
const REPORT_SEPARATOR = Buffer.from('\n\n', 'ascii');
const REPORT_TRAILING_NEWLINE = Buffer.from('\n', 'ascii');
const SHA_256_HASH_LENGTH = 32;
export class SnapshotError extends Error {
constructor(message, snapPath) {
super(message);
this.name = 'SnapshotError';
this.snapPath = snapPath;
}
}
export class ChecksumError extends SnapshotError {
constructor(snapPath) {
super('Checksum mismatch', snapPath);
this.name = 'ChecksumError';
}
}
export class VersionMismatchError extends SnapshotError {
constructor(snapPath, version) {
super('Unexpected snapshot version', snapPath);
this.name = 'VersionMismatchError';
this.snapVersion = version;
this.expectedVersion = VERSION;
}
}
export class InvalidSnapshotError extends SnapshotError {
constructor(snapPath) {
super('Invalid snapshot file', snapPath);
this.name = 'InvalidSnapshotError';
}
}
const LEGACY_SNAPSHOT_HEADER = Buffer.from('// Jest Snapshot v1');
function isLegacySnapshot(buffer) {
return LEGACY_SNAPSHOT_HEADER.equals(buffer.slice(0, LEGACY_SNAPSHOT_HEADER.byteLength));
}
export class LegacyError extends SnapshotError {
constructor(snapPath) {
super('Legacy snapshot file', snapPath);
this.name = 'LegacyError';
}
}
function tryRead(file) {
try {
return fs.readFileSync(file);
} catch (error) {
if (error.code === 'ENOENT') {
return null;
}
throw error;
}
}
function formatEntry(snapshot, index) {
const {
data,
label = `Snapshot ${index + 1}`, // Human-readable labels start counting at 1.
} = snapshot;
const description = data
? concordance.formatDescriptor(concordance.deserialize(data), concordanceOptions)
: '<No Data>';
const blockquote = label.split(/\n/).map(line => '> ' + line).join('\n');
return `${blockquote}\n\n${indentString(description, 4)}`;
}
function combineEntries({blocks}) {
const combined = new BufferBuilder();
for (const {title, snapshots} of blocks) {
const last = snapshots[snapshots.length - 1];
combined.write(`\n\n## ${title}\n\n`);
for (const [index, snapshot] of snapshots.entries()) {
combined.write(formatEntry(snapshot, index));
if (snapshot !== last) {
combined.write(REPORT_SEPARATOR);
}
}
}
return combined;
}
function generateReport(relFile, snapFile, snapshots) {
return new BufferBuilder()
.write(`# Snapshot report for \`${slash(relFile)}\`
The actual snapshot is saved in \`${snapFile}\`.
Generated by [AVA](https://avajs.dev).`)
.append(combineEntries(snapshots))
.write(REPORT_TRAILING_NEWLINE)
.toBuffer();
}
class BufferBuilder {
constructor() {
this.buffers = [];
this.byteOffset = 0;
}
append(builder) {
this.buffers.push(...builder.buffers);
this.byteOffset += builder.byteOffset;
return this;
}
write(data) {
if (typeof data === 'string') {
this.write(Buffer.from(data, 'utf8'));
} else {
this.buffers.push(data);
this.byteOffset += data.byteLength;
}
return this;
}
toBuffer() {
return Buffer.concat(this.buffers, this.byteOffset);
}
}
function sortBlocks(blocksByTitle, blockIndices) {
return [...blocksByTitle].sort(
([aTitle], [bTitle]) => {
const a = blockIndices.get(aTitle);
const b = blockIndices.get(bTitle);
if (a === undefined) {
if (b === undefined) {
return 0;
}
return 1;
}
if (b === undefined) {
return -1;
}
return a - b;
},
);
}
async function encodeSnapshots(snapshotData) {
const encoded = await cbor.encodeAsync(snapshotData, {
omitUndefinedProperties: true,
canonical: true,
});
const compressed = zlib.gzipSync(encoded);
compressed[9] = 0x03; // Override the GZip header containing the OS to always be Linux
const sha256sum = crypto.createHash('sha256').update(compressed).digest();
return Buffer.concat([
READABLE_PREFIX,
VERSION_HEADER,
sha256sum,
compressed,
], READABLE_PREFIX.byteLength + VERSION_HEADER.byteLength + SHA_256_HASH_LENGTH + compressed.byteLength);
}
function decodeSnapshots(buffer, snapPath) {
if (isLegacySnapshot(buffer)) {
throw new LegacyError(snapPath);
}
// The version starts after the readable prefix, which is ended by a newline
// byte (0x0A).
const newline = buffer.indexOf(0x0A);
if (newline === -1) {
throw new InvalidSnapshotError(snapPath);
}
const versionOffset = newline + 1;
const version = buffer.readUInt16LE(versionOffset);
if (version !== VERSION) {
throw new VersionMismatchError(snapPath, version);
}
const sha256sumOffset = versionOffset + 2;
const compressedOffset = sha256sumOffset + SHA_256_HASH_LENGTH;
const compressed = buffer.slice(compressedOffset);
const sha256sum = crypto.createHash('sha256').update(compressed).digest();
const expectedSum = buffer.slice(sha256sumOffset, compressedOffset);
if (!sha256sum.equals(expectedSum)) {
throw new ChecksumError(snapPath);
}
const decompressed = zlib.gunzipSync(compressed);
return cbor.decode(decompressed);
}
class Manager {
constructor(options) {
this.dir = options.dir;
this.recordNewSnapshots = options.recordNewSnapshots;
this.updating = options.updating;
this.relFile = options.relFile;
this.reportFile = options.reportFile;
this.reportPath = options.reportPath;
this.snapFile = options.snapFile;
this.snapPath = options.snapPath;
this.oldBlocksByTitle = options.oldBlocksByTitle;
this.newBlocksByTitle = options.newBlocksByTitle;
this.blockIndices = new Map();
this.error = options.error;
this.hasChanges = false;
}
touch(title, taskIndex) {
this.blockIndices.set(title, taskIndex);
}
compare(options) {
if (this.error) {
throw this.error;
}
const block = this.newBlocksByTitle.get(options.belongsTo);
const snapshot = block && block.snapshots[options.index];
const data = snapshot && snapshot.data;
if (!data) {
if (!this.recordNewSnapshots) {
return {pass: false};
}
if (options.deferRecording) {
const record = this.deferRecord(options);
return {pass: true, record};
}
this.record(options);
return {pass: true};
}
const actual = concordance.deserialize(data, concordanceOptions);
const expected = concordance.describe(options.expected, concordanceOptions);
const pass = concordance.compareDescriptors(actual, expected);
return {actual, expected, pass};
}
recordSerialized({data, label, belongsTo, index}) {
let block = this.newBlocksByTitle.get(belongsTo);
if (!block) {
block = {snapshots: []};
}
const {snapshots} = block;
if (index > snapshots.length) {
throw new RangeError(`Cannot record snapshot ${index} for ${JSON.stringify(belongsTo)}, exceeds expected index of ${snapshots.length}`);
} else if (index < snapshots.length) {
if (snapshots[index].data) {
throw new RangeError(`Cannot record snapshot ${index} for ${JSON.stringify(belongsTo)}, already exists`);
}
snapshots[index] = {data, label};
} else {
snapshots.push({data, label});
}
this.newBlocksByTitle.set(belongsTo, block);
}
deferRecord(options) {
const {expected, belongsTo, label, index} = options;
const descriptor = concordance.describe(expected, concordanceOptions);
const data = concordance.serialize(descriptor);
return () => { // Must be called in order!
this.hasChanges = true;
this.recordSerialized({data, label, belongsTo, index});
};
}
record(options) {
const record = this.deferRecord(options);
record();
}
skipBlock(title) {
const block = this.oldBlocksByTitle.get(title);
if (block) {
this.newBlocksByTitle.set(title, block);
}
}
skipSnapshot({belongsTo, index, deferRecording}) {
const oldBlock = this.oldBlocksByTitle.get(belongsTo);
let snapshot = oldBlock && oldBlock.snapshots[index];
if (!snapshot) {
snapshot = {};
}
// Retain the label from the old snapshot, so as not to assume that the
// snapshot.skip() arguments are well-formed.
// Defer recording if called in a try().
if (deferRecording) {
return () => { // Must be called in order!
this.recordSerialized({belongsTo, index, ...snapshot});
};
}
this.recordSerialized({belongsTo, index, ...snapshot});
}
async save() {
const {dir, relFile, snapFile, snapPath, reportPath} = this;
if (this.updating && this.newBlocksByTitle.size === 0) {
return {
changedFiles: [cleanFile(snapPath), cleanFile(reportPath)].flat(),
temporaryFiles: [],
};
}
if (!this.hasChanges) {
return null;
}
const snapshots = {
blocks: sortBlocks(this.newBlocksByTitle, this.blockIndices).map(
([title, block]) => ({title, ...block}),
),
};
const buffer = await encodeSnapshots(snapshots);
const reportBuffer = generateReport(relFile, snapFile, snapshots);
await fs.promises.mkdir(dir, {recursive: true});
const temporaryFiles = [];
const tmpfileCreated = file => temporaryFiles.push(file);
await Promise.all([
writeFileAtomic(snapPath, buffer, {tmpfileCreated}),
writeFileAtomic(reportPath, reportBuffer, {tmpfileCreated}),
]);
return {
changedFiles: [snapPath, reportPath],
temporaryFiles,
};
}
}
const resolveSourceFile = mem(file => {
const sourceMap = findSourceMap(file);
// Prior to Node.js 18.8.0, the value when a source map could not be found was `undefined`.
// This changed to `null` in <https://github.com/nodejs/node/pull/43875>. Check both.
if (sourceMap === undefined || sourceMap === null) {
return file;
}
const {payload} = sourceMap;
if (payload.sources.length === 0) { // Hypothetical?
return file;
}
return payload.sources[0].startsWith('file://')
? fileURLToPath(payload.sources[0])
: payload.sources[0];
});
export const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
const testDir = path.dirname(resolveSourceFile(file));
if (fixedLocation) {
const relativeTestLocation = path.relative(projectDir, testDir);
return path.join(fixedLocation, relativeTestLocation);
}
const parts = new Set(path.relative(projectDir, testDir).split(path.sep));
if (parts.has('__tests__')) {
return path.join(testDir, '__snapshots__');
}
if (parts.has('test') || parts.has('tests')) { // Accept tests, even though it's not in the default test patterns
return path.join(testDir, 'snapshots');
}
return testDir;
}, {cacheKey: ([{file}]) => file});
function determineSnapshotPaths({file, fixedLocation, projectDir}) {
const dir = determineSnapshotDir({file, fixedLocation, projectDir});
const relFile = path.relative(projectDir, resolveSourceFile(file));
const name = path.basename(relFile);
const reportFile = `${name}.md`;
const snapFile = `${name}.snap`;
return {
dir,
relFile,
snapFile,
reportFile,
snapPath: path.join(dir, snapFile),
reportPath: path.join(dir, reportFile),
};
}
function cleanFile(file) {
try {
fs.unlinkSync(file);
return [file];
} catch (error) {
if (error.code === 'ENOENT') {
return [];
}
throw error;
}
}
export function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
// Keep runner unit tests that use `new Runner()` happy
if (file === undefined || projectDir === undefined) {
return new Manager({
recordNewSnapshots,
updating,
oldBlocksByTitle: new Map(),
newBlocksByTitle: new Map(),
});
}
const paths = determineSnapshotPaths({file, fixedLocation, projectDir});
const buffer = tryRead(paths.snapPath);
if (!buffer) {
return new Manager({
recordNewSnapshots,
updating,
...paths,
oldBlocksByTitle: new Map(),
newBlocksByTitle: new Map(),
});
}
let blocksByTitle;
let snapshotError;
try {
const data = decodeSnapshots(buffer, paths.snapPath);
blocksByTitle = new Map(data.blocks.map(({title, ...block}) => [title, block]));
} catch (error) {
blocksByTitle = new Map();
if (!updating) { // Discard all decoding errors when updating snapshots
snapshotError = error instanceof SnapshotError
? error
: new InvalidSnapshotError(paths.snapPath);
}
}
return new Manager({
recordNewSnapshots,
updating,
...paths,
oldBlocksByTitle: blocksByTitle,
newBlocksByTitle: updating ? new Map() : blocksByTitle,
error: snapshotError,
});
}

643
github/codeql-action-v2/node_modules/ava/lib/test.js generated vendored Normal file
View File

@@ -0,0 +1,643 @@
import concordance from 'concordance';
import isPromise from 'is-promise';
import plur from 'plur';
import {AssertionError, Assertions, checkAssertionMessage} from './assert.js';
import concordanceOptions from './concordance-options.js';
import nowAndTimers from './now-and-timers.cjs';
import parseTestArgs from './parse-test-args.js';
const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop);
function isExternalAssertError(error) {
if (typeof error !== 'object' || error === null) {
return false;
}
// Match errors thrown by <https://www.npmjs.com/package/expect>.
if (hasOwnProperty(error, 'matcherResult')) {
return true;
}
// Match errors thrown by <https://www.npmjs.com/package/chai> and <https://nodejs.org/api/assert.html>.
return hasOwnProperty(error, 'actual') && hasOwnProperty(error, 'expected');
}
function formatErrorValue(label, error) {
const formatted = concordance.format(error, concordanceOptions);
return {label, formatted};
}
const captureSavedError = () => {
const limitBefore = Error.stackTraceLimit;
Error.stackTraceLimit = 1;
const error = new Error(); // eslint-disable-line unicorn/error-message
Error.stackTraceLimit = limitBefore;
return error;
};
const testMap = new WeakMap();
class ExecutionContext extends Assertions {
constructor(test) {
super({
pass() {
test.countPassedAssertion();
},
pending(promise) {
test.addPendingAssertion(promise);
},
fail(error) {
test.addFailedAssertion(error);
},
skip() {
test.countPassedAssertion();
},
compareWithSnapshot: options => test.compareWithSnapshot(options),
experiments: test.experiments,
disableSnapshots: test.isHook === true,
});
testMap.set(this, test);
this.snapshot.skip = () => {
test.skipSnapshot();
};
this.log = (...inputArgs) => {
const args = inputArgs.map(value => typeof value === 'string'
? value
: concordance.format(value, concordanceOptions));
if (args.length > 0) {
test.addLog(args.join(' '));
}
};
this.plan = count => {
test.plan(count, captureSavedError());
};
this.plan.skip = () => {};
this.timeout = (ms, message) => {
test.timeout(ms, message);
};
this.teardown = callback => {
test.addTeardown(callback);
};
this.try = async (...attemptArgs) => {
if (test.isHook) {
const error = new Error('`t.try()` can only be used in tests');
test.saveFirstError(error);
throw error;
}
const {args, implementation, title} = parseTestArgs(attemptArgs);
if (!implementation) {
throw new TypeError('Expected an implementation.');
}
if (Array.isArray(implementation)) {
throw new TypeError('AVA 4 no longer supports t.try() with multiple implementations.');
}
let attemptTitle;
if (!title.isSet || title.isEmpty) {
attemptTitle = `${test.title} ─ attempt ${test.attemptCount + 1}`;
} else if (title.isValid) {
attemptTitle = `${test.title}${title.value}`;
} else {
throw new TypeError('`t.try()` titles must be strings');
}
if (!test.registerUniqueTitle(attemptTitle)) {
throw new Error(`Duplicate test title: ${attemptTitle}`);
}
let committed = false;
let discarded = false;
const {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount} = await test.runAttempt(attemptTitle, t => implementation(t, ...args));
return {
errors,
logs: [...logs], // Don't allow modification of logs.
passed,
title: attemptTitle,
commit({retainLogs = true} = {}) {
if (committed) {
return;
}
if (discarded) {
test.saveFirstError(new Error('Cant commit a result that was previously discarded'));
return;
}
committed = true;
test.finishAttempt({
assertCount,
commit: true,
deferredSnapshotRecordings,
errors,
logs,
passed,
retainLogs,
snapshotCount,
startingSnapshotCount,
});
},
discard({retainLogs = false} = {}) {
if (committed) {
test.saveFirstError(new Error('Cant discard a result that was previously committed'));
return;
}
if (discarded) {
return;
}
discarded = true;
test.finishAttempt({
assertCount: 0,
commit: false,
deferredSnapshotRecordings,
errors,
logs,
passed,
retainLogs,
snapshotCount,
startingSnapshotCount,
});
},
};
};
}
get title() {
return testMap.get(this).title;
}
get context() {
return testMap.get(this).contextRef.get();
}
set context(context) {
testMap.get(this).contextRef.set(context);
}
get passed() {
const test = testMap.get(this);
return test.isHook ? test.testPassed : !test.assertError;
}
}
export default class Test {
constructor(options) {
this.contextRef = options.contextRef;
this.experiments = options.experiments || {};
this.failWithoutAssertions = options.failWithoutAssertions;
this.fn = options.fn;
this.isHook = options.isHook === true;
this.metadata = options.metadata;
this.title = options.title;
this.testPassed = options.testPassed;
this.registerUniqueTitle = options.registerUniqueTitle;
this.logs = [];
this.teardowns = [];
this.notifyTimeoutUpdate = options.notifyTimeoutUpdate;
const {snapshotBelongsTo = this.title, nextSnapshotIndex = 0} = options;
this.snapshotBelongsTo = snapshotBelongsTo;
this.nextSnapshotIndex = nextSnapshotIndex;
this.snapshotCount = 0;
const deferRecording = this.metadata.inline;
this.deferredSnapshotRecordings = [];
this.compareWithSnapshot = ({expected, message}) => {
this.snapshotCount++;
const belongsTo = snapshotBelongsTo;
const index = this.nextSnapshotIndex++;
const label = message;
const {record, ...result} = options.compareTestSnapshot({
belongsTo,
deferRecording,
expected,
index,
label,
taskIndex: this.metadata.taskIndex,
});
if (record) {
this.deferredSnapshotRecordings.push(record);
}
return result;
};
this.skipSnapshot = () => {
if (typeof options.skipSnapshot === 'function') {
const record = options.skipSnapshot({
belongsTo: snapshotBelongsTo,
index: this.nextSnapshotIndex,
deferRecording,
taskIndex: this.metadata.taskIndex,
});
if (record) {
this.deferredSnapshotRecordings.push(record);
}
}
this.nextSnapshotIndex++;
this.snapshotCount++;
this.countPassedAssertion();
};
this.runAttempt = async (title, fn) => {
if (this.finishing) {
this.saveFirstError(new Error('Running a `t.try()`, but the test has already finished'));
}
this.attemptCount++;
this.pendingAttemptCount++;
const {contextRef, snapshotBelongsTo, nextSnapshotIndex, snapshotCount: startingSnapshotCount} = this;
const attempt = new Test({
...options,
fn,
metadata: {...options.metadata, failing: false, inline: true},
contextRef: contextRef.copy(),
snapshotBelongsTo,
nextSnapshotIndex,
title,
});
const {deferredSnapshotRecordings, error, logs, passed, assertCount, snapshotCount} = await attempt.run();
const errors = error ? [error] : [];
return {assertCount, deferredSnapshotRecordings, errors, logs, passed, snapshotCount, startingSnapshotCount};
};
this.assertCount = 0;
this.assertError = undefined;
this.attemptCount = 0;
this.calledEnd = false;
this.duration = null;
this.finishDueToInactivity = null;
this.finishDueToTimeout = null;
this.finishing = false;
this.pendingAssertionCount = 0;
this.pendingAttemptCount = 0;
this.planCount = null;
this.startedAt = 0;
this.timeoutMs = 0;
this.timeoutTimer = null;
}
createExecutionContext() {
return new ExecutionContext(this);
}
countPassedAssertion() {
if (this.finishing) {
this.saveFirstError(new Error('Assertion passed, but test has already finished'));
}
if (this.pendingAttemptCount > 0) {
this.saveFirstError(new Error('Assertion passed, but an attempt is pending. Use the attempts assertions instead'));
}
this.assertCount++;
this.refreshTimeout();
}
addLog(text) {
this.logs.push(text);
}
addPendingAssertion(promise) {
if (this.finishing) {
this.saveFirstError(new Error('Assertion started, but test has already finished'));
}
if (this.pendingAttemptCount > 0) {
this.saveFirstError(new Error('Assertion started, but an attempt is pending. Use the attempts assertions instead'));
}
this.assertCount++;
this.pendingAssertionCount++;
this.refreshTimeout();
promise
.catch(error => this.saveFirstError(error))
.then(() => {
this.pendingAssertionCount--;
this.refreshTimeout();
});
}
addFailedAssertion(error) {
if (this.finishing) {
this.saveFirstError(new Error('Assertion failed, but test has already finished'));
}
if (this.pendingAttemptCount > 0) {
this.saveFirstError(new Error('Assertion failed, but an attempt is pending. Use the attempts assertions instead'));
}
this.assertCount++;
this.refreshTimeout();
this.saveFirstError(error);
}
finishAttempt({commit, deferredSnapshotRecordings, errors, logs, passed, retainLogs, snapshotCount, startingSnapshotCount}) {
if (this.finishing) {
if (commit) {
this.saveFirstError(new Error('`t.try()` result was committed, but the test has already finished'));
} else {
this.saveFirstError(new Error('`t.try()` result was discarded, but the test has already finished'));
}
}
if (commit) {
this.assertCount++;
if (startingSnapshotCount === this.snapshotCount) {
this.snapshotCount += snapshotCount;
this.nextSnapshotIndex += snapshotCount;
for (const record of deferredSnapshotRecordings) {
record();
}
} else {
this.saveFirstError(new Error('Cannot commit `t.try()` result. Do not run concurrent snapshot assertions when using `t.try()`'));
}
}
this.pendingAttemptCount--;
if (commit && !passed) {
this.saveFirstError(errors[0]);
}
if (retainLogs) {
for (const log of logs) {
this.addLog(log);
}
}
this.refreshTimeout();
}
saveFirstError(error) {
if (!this.assertError) {
this.assertError = error;
}
}
plan(count, planError) {
if (typeof count !== 'number') {
throw new TypeError('Expected a number');
}
this.planCount = count;
// In case the `planCount` doesn't match `assertCount, we need the stack of
// this function to throw with a useful stack.
this.planError = planError;
}
timeout(ms, message) {
const result = checkAssertionMessage('timeout', message);
if (result !== true) {
this.saveFirstError(result);
// Allow the timeout to be set even when the message is invalid.
message = '';
}
if (this.finishing) {
return;
}
this.clearTimeout();
this.timeoutMs = ms;
this.timeoutTimer = nowAndTimers.setCappedTimeout(() => {
this.saveFirstError(new Error(message || 'Test timeout exceeded'));
if (this.finishDueToTimeout) {
this.finishDueToTimeout();
}
}, ms);
this.notifyTimeoutUpdate(this.timeoutMs);
}
refreshTimeout() {
if (!this.timeoutTimer) {
return;
}
if (this.timeoutTimer.refresh) {
this.timeoutTimer.refresh();
} else {
this.timeout(this.timeoutMs);
}
}
clearTimeout() {
nowAndTimers.clearTimeout(this.timeoutTimer);
this.timeoutTimer = null;
}
addTeardown(callback) {
if (this.isHook) {
this.saveFirstError(new Error('`t.teardown()` is not allowed in hooks'));
return;
}
if (this.finishing) {
this.saveFirstError(new Error('`t.teardown()` cannot be used during teardown'));
return;
}
if (typeof callback !== 'function') {
throw new TypeError('Expected a function');
}
this.teardowns.push(callback);
}
async runTeardowns() {
const teardowns = [...this.teardowns].reverse();
for (const teardown of teardowns) {
try {
await teardown(); // eslint-disable-line no-await-in-loop
} catch (error) {
this.saveFirstError(error);
}
}
}
verifyPlan() {
if (!this.assertError && this.planCount !== null && this.planCount !== this.assertCount) {
this.saveFirstError(new AssertionError({
assertion: 'plan',
message: `Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertCount}.`,
operator: '===',
savedError: this.planError,
}));
}
}
verifyAssertions() {
if (this.assertError) {
return;
}
if (this.pendingAttemptCount > 0) {
this.saveFirstError(new Error('Test finished, but not all attempts were committed or discarded'));
return;
}
if (this.pendingAssertionCount > 0) {
this.saveFirstError(new Error('Test finished, but an assertion is still pending'));
return;
}
if (this.failWithoutAssertions) {
if (this.planCount !== null) {
return; // `verifyPlan()` will report an error already.
}
if (this.assertCount === 0 && !this.calledEnd) {
this.saveFirstError(new Error('Test finished without running any assertions'));
}
}
}
callFn() {
try {
return {
ok: true,
retval: this.fn.call(null, this.createExecutionContext()),
};
} catch (error) {
return {
ok: false,
error,
};
}
}
run() {
this.startedAt = nowAndTimers.now();
const result = this.callFn();
if (!result.ok) {
if (isExternalAssertError(result.error)) {
this.saveFirstError(new AssertionError({
message: 'Assertion failed',
savedError: result.error instanceof Error && result.error,
values: [{label: 'Assertion failed: ', formatted: result.error.message}],
}));
} else {
this.saveFirstError(new AssertionError({
message: 'Error thrown in test',
savedError: result.error instanceof Error && result.error,
values: [formatErrorValue('Error thrown in test:', result.error)],
}));
}
return this.finish();
}
const returnedObservable = result.retval !== null && typeof result.retval === 'object' && typeof result.retval.subscribe === 'function';
const returnedPromise = isPromise(result.retval);
let promise;
if (returnedObservable) {
promise = new Promise((resolve, reject) => {
result.retval.subscribe({
error: reject,
complete: () => resolve(),
});
});
} else if (returnedPromise) {
// `retval` can be any thenable, so convert to a proper promise.
promise = Promise.resolve(result.retval);
}
if (promise) {
return new Promise(resolve => {
this.finishDueToAttributedError = () => {
resolve(this.finish());
};
this.finishDueToTimeout = () => {
resolve(this.finish());
};
this.finishDueToInactivity = () => {
const error = returnedObservable
? new Error('Observable returned by test never completed')
: new Error('Promise returned by test never resolved');
this.saveFirstError(error);
resolve(this.finish());
};
promise
.catch(error => {
if (isExternalAssertError(error)) {
this.saveFirstError(new AssertionError({
message: 'Assertion failed',
savedError: error instanceof Error && error,
values: [{label: 'Assertion failed: ', formatted: error.message}],
}));
} else {
this.saveFirstError(new AssertionError({
message: 'Rejected promise returned by test',
savedError: error instanceof Error && error,
values: [formatErrorValue('Rejected promise returned by test. Reason:', error)],
}));
}
})
.then(() => resolve(this.finish()));
});
}
return this.finish();
}
async finish() {
this.finishing = true;
this.clearTimeout();
this.verifyPlan();
this.verifyAssertions();
await this.runTeardowns();
this.duration = nowAndTimers.now() - this.startedAt;
let error = this.assertError;
let passed = !error;
if (this.metadata.failing) {
passed = !passed;
error = passed ? null : new Error('Test was expected to fail, but succeeded, you should stop marking the test as failing');
}
return {
deferredSnapshotRecordings: this.deferredSnapshotRecordings,
duration: this.duration,
error,
logs: this.logs,
metadata: this.metadata,
passed,
snapshotCount: this.snapshotCount,
assertCount: this.assertCount,
title: this.title,
};
}
}

476
github/codeql-action-v2/node_modules/ava/lib/watcher.js generated vendored Normal file
View File

@@ -0,0 +1,476 @@
import nodePath from 'node:path';
import chokidar_ from 'chokidar';
import createDebug from 'debug';
import {chalk} from './chalk.js';
import {applyTestFileFilter, classify, getChokidarIgnorePatterns} from './globs.js';
let chokidar = chokidar_;
export function _testOnlyReplaceChokidar(replacement) {
chokidar = replacement;
}
let debug = createDebug('ava:watcher');
export function _testOnlyReplaceDebug(replacement) {
debug = replacement('ava:watcher');
}
function rethrowAsync(error) {
// Don't swallow exceptions. Note that any
// expected error should already have been logged
setImmediate(() => {
throw error;
});
}
const MIN_DEBOUNCE_DELAY = 10;
const INITIAL_DEBOUNCE_DELAY = 100;
const END_MESSAGE = chalk.gray('Type `r` and press enter to rerun tests\nType `u` and press enter to update snapshots\n');
class Debouncer {
constructor(watcher) {
this.watcher = watcher;
this.timer = null;
this.repeat = false;
}
debounce(delay) {
if (this.timer) {
this.again = true;
return;
}
delay = delay ? Math.max(delay, MIN_DEBOUNCE_DELAY) : INITIAL_DEBOUNCE_DELAY;
const timer = setTimeout(async () => {
await this.watcher.busy;
// Do nothing if debouncing was canceled while waiting for the busy
// promise to fulfil
if (this.timer !== timer) {
return;
}
if (this.again) {
this.timer = null;
this.again = false;
this.debounce(delay / 2);
} else {
this.watcher.runAfterChanges();
this.timer = null;
this.again = false;
}
}, delay);
this.timer = timer;
}
cancel() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
this.again = false;
}
}
}
class TestDependency {
constructor(file, dependencies) {
this.file = file;
this.dependencies = dependencies;
}
contains(dependency) {
return this.dependencies.includes(dependency);
}
}
export default class Watcher {
constructor({api, filter = [], globs, projectDir, providers, reporter}) {
this.debouncer = new Debouncer(this);
this.clearLogOnNextRun = true;
this.runVector = 0;
this.previousFiles = [];
this.globs = {cwd: projectDir, ...globs};
const patternFilters = filter.map(({pattern}) => pattern);
this.providers = providers;
this.run = (specificFiles = [], updateSnapshots = false) => {
const clearLogOnNextRun = this.clearLogOnNextRun && this.runVector > 0;
if (this.runVector > 0) {
this.clearLogOnNextRun = true;
}
this.runVector++;
let runOnlyExclusive = false;
if (specificFiles.length > 0) {
const exclusiveFiles = specificFiles.filter(file => this.filesWithExclusiveTests.includes(file));
runOnlyExclusive = exclusiveFiles.length !== this.filesWithExclusiveTests.length;
if (runOnlyExclusive) {
// The test files that previously contained exclusive tests are always
// run, together with the remaining specific files.
const remainingFiles = specificFiles.filter(file => !exclusiveFiles.includes(file));
specificFiles = [...this.filesWithExclusiveTests, ...remainingFiles];
}
if (filter.length > 0) {
specificFiles = applyTestFileFilter({
cwd: projectDir,
expandDirectories: false,
filter: patternFilters,
testFiles: specificFiles,
treatFilterPatternsAsFiles: false,
});
}
this.pruneFailures(specificFiles);
}
this.touchedFiles.clear();
this.previousFiles = specificFiles;
this.busy = api.run({
files: specificFiles,
filter,
runtimeOptions: {
clearLogOnNextRun,
previousFailures: this.sumPreviousFailures(this.runVector),
runOnlyExclusive,
runVector: this.runVector,
updateSnapshots: updateSnapshots === true,
},
})
.then(runStatus => {
reporter.endRun();
reporter.lineWriter.writeLine(END_MESSAGE);
if (this.clearLogOnNextRun && (
runStatus.stats.failedHooks > 0
|| runStatus.stats.failedTests > 0
|| runStatus.stats.failedWorkers > 0
|| runStatus.stats.internalErrors > 0
|| runStatus.stats.timeouts > 0
|| runStatus.stats.uncaughtExceptions > 0
|| runStatus.stats.unhandledRejections > 0
)) {
this.clearLogOnNextRun = false;
}
})
.catch(rethrowAsync);
};
this.testDependencies = [];
this.trackTestDependencies(api);
this.temporaryFiles = new Set();
this.touchedFiles = new Set();
this.trackTouchedFiles(api);
this.filesWithExclusiveTests = [];
this.trackExclusivity(api);
this.filesWithFailures = [];
this.trackFailures(api);
this.dirtyStates = {};
this.watchFiles();
this.rerunAll();
}
watchFiles() {
chokidar.watch(['**/*'], {
cwd: this.globs.cwd,
ignored: getChokidarIgnorePatterns(this.globs),
ignoreInitial: true,
}).on('all', (event, path) => {
if (event === 'add' || event === 'change' || event === 'unlink') {
debug('Detected %s of %s', event, path);
this.dirtyStates[nodePath.join(this.globs.cwd, path)] = event;
this.debouncer.debounce();
}
});
}
trackTestDependencies(api) {
api.on('run', plan => {
plan.status.on('stateChange', evt => {
if (evt.type !== 'dependencies') {
return;
}
const dependencies = evt.dependencies.filter(filePath => {
const {isIgnoredByWatcher} = classify(filePath, this.globs);
return !isIgnoredByWatcher;
});
this.updateTestDependencies(evt.testFile, dependencies);
});
});
}
updateTestDependencies(file, dependencies) {
// Ensure the rewritten test file path is included in the dependencies,
// since changes to non-rewritten paths are ignored.
for (const {main} of this.providers) {
const rewritten = main.resolveTestFile(file);
if (!dependencies.includes(rewritten)) {
dependencies = [rewritten, ...dependencies];
}
}
if (dependencies.length === 0) {
this.testDependencies = this.testDependencies.filter(dep => dep.file !== file);
return;
}
const isUpdate = this.testDependencies.some(dep => {
if (dep.file !== file) {
return false;
}
dep.dependencies = dependencies;
return true;
});
if (!isUpdate) {
this.testDependencies.push(new TestDependency(file, dependencies));
}
}
trackTouchedFiles(api) {
api.on('run', plan => {
plan.status.on('stateChange', evt => {
if (evt.type !== 'touched-files') {
return;
}
for (const file of evt.files.changedFiles) {
this.touchedFiles.add(file);
}
for (const file of evt.files.temporaryFiles) {
this.temporaryFiles.add(file);
}
});
});
}
trackExclusivity(api) {
api.on('run', plan => {
plan.status.on('stateChange', evt => {
if (evt.type !== 'worker-finished') {
return;
}
const fileStats = plan.status.stats.byFile.get(evt.testFile);
const ranExclusiveTests = fileStats.selectedTests > 0 && fileStats.declaredTests > fileStats.selectedTests;
this.updateExclusivity(evt.testFile, ranExclusiveTests);
});
});
}
updateExclusivity(file, hasExclusiveTests) {
const index = this.filesWithExclusiveTests.indexOf(file);
if (hasExclusiveTests && index === -1) {
this.filesWithExclusiveTests.push(file);
} else if (!hasExclusiveTests && index !== -1) {
this.filesWithExclusiveTests.splice(index, 1);
}
}
trackFailures(api) {
api.on('run', plan => {
this.pruneFailures(plan.files);
const currentVector = this.runVector;
plan.status.on('stateChange', evt => {
if (!evt.testFile) {
return;
}
switch (evt.type) {
case 'hook-failed':
case 'internal-error':
case 'process-exit':
case 'test-failed':
case 'uncaught-exception':
case 'unhandled-rejection':
case 'worker-failed': {
this.countFailure(evt.testFile, currentVector);
break;
}
default: {
break;
}
}
});
});
}
pruneFailures(files) {
const toPrune = new Set(files);
this.filesWithFailures = this.filesWithFailures.filter(state => !toPrune.has(state.file));
}
countFailure(file, vector) {
const isUpdate = this.filesWithFailures.some(state => {
if (state.file !== file) {
return false;
}
state.count++;
return true;
});
if (!isUpdate) {
this.filesWithFailures.push({
file,
vector,
count: 1,
});
}
}
sumPreviousFailures(beforeVector) {
let total = 0;
for (const state of this.filesWithFailures) {
if (state.vector < beforeVector) {
total += state.count;
}
}
return total;
}
cleanUnlinkedTests(unlinkedTests) {
for (const testFile of unlinkedTests) {
this.updateTestDependencies(testFile, []);
this.updateExclusivity(testFile, false);
this.pruneFailures([testFile]);
}
}
observeStdin(stdin) {
stdin.resume();
stdin.setEncoding('utf8');
stdin.on('data', async data => {
data = data.trim().toLowerCase();
if (data !== 'r' && data !== 'rs' && data !== 'u') {
return;
}
// Cancel the debouncer, it might rerun specific tests whereas *all* tests
// need to be rerun
this.debouncer.cancel();
await this.busy;
// Cancel the debouncer again, it might have restarted while waiting for
// the busy promise to fulfil
this.debouncer.cancel();
this.clearLogOnNextRun = false;
if (data === 'u') {
this.updatePreviousSnapshots();
} else {
this.rerunAll();
}
});
}
rerunAll() {
this.dirtyStates = {};
this.run();
}
updatePreviousSnapshots() {
this.dirtyStates = {};
this.run(this.previousFiles, true);
}
runAfterChanges() {
const {dirtyStates} = this;
this.dirtyStates = {};
let dirtyPaths = Object.keys(dirtyStates).filter(path => {
if (this.touchedFiles.has(path)) {
debug('Ignoring known touched file %s', path);
this.touchedFiles.delete(path);
return false;
}
// Unlike touched files, temporary files are never cleared. We may see
// adds and unlinks detected separately, so we track the temporary files
// as long as AVA is running.
if (this.temporaryFiles.has(path)) {
debug('Ignoring known temporary file %s', path);
return false;
}
return true;
});
for (const {main} of this.providers) {
dirtyPaths = dirtyPaths.filter(path => {
if (main.ignoreChange(path)) {
debug('Ignoring changed file %s', path);
return false;
}
return true;
});
}
const dirtyHelpersAndSources = [];
const addedOrChangedTests = [];
const unlinkedTests = [];
for (const filePath of dirtyPaths) {
const {isIgnoredByWatcher, isTest} = classify(filePath, this.globs);
if (!isIgnoredByWatcher) {
if (isTest) {
if (dirtyStates[filePath] === 'unlink') {
unlinkedTests.push(filePath);
} else {
addedOrChangedTests.push(filePath);
}
} else {
dirtyHelpersAndSources.push(filePath);
}
}
}
this.cleanUnlinkedTests(unlinkedTests);
// No need to rerun tests if the only change is that tests were deleted
if (unlinkedTests.length === dirtyPaths.length) {
return;
}
if (dirtyHelpersAndSources.length === 0) {
// Run any new or changed tests
this.run(addedOrChangedTests);
return;
}
// Try to find tests that depend on the changed source files
const testsByHelpersOrSource = dirtyHelpersAndSources.map(path => this.testDependencies.filter(dep => dep.contains(path)).map(dep => {
debug('%s is a dependency of %s', path, dep.file);
return dep.file;
})).filter(tests => tests.length > 0);
// Rerun all tests if source files were changed that could not be traced to
// specific tests
if (testsByHelpersOrSource.length !== dirtyHelpersAndSources.length) {
debug('Files remain that cannot be traced to specific tests: %O', dirtyHelpersAndSources);
debug('Rerunning all tests');
this.run();
return;
}
// Run all affected tests
this.run([...new Set([addedOrChangedTests, testsByHelpersOrSource].flat(2))]);
}
}

View File

@@ -0,0 +1,239 @@
import {createRequire} from 'node:module';
import process from 'node:process';
import {pathToFileURL} from 'node:url';
import {workerData} from 'node:worker_threads';
import setUpCurrentlyUnhandled from 'currently-unhandled';
import {set as setChalk} from '../chalk.js';
import nowAndTimers from '../now-and-timers.cjs';
import providerManager from '../provider-manager.js';
import Runner from '../runner.js';
import serializeError from '../serialize-error.js';
import channel from './channel.cjs';
import dependencyTracking from './dependency-tracker.js';
import lineNumberSelection from './line-numbers.js';
import {set as setOptions} from './options.cjs';
import {flags, refs, sharedWorkerTeardowns} from './state.cjs';
import {isRunningInThread, isRunningInChildProcess} from './utils.cjs';
const currentlyUnhandled = setUpCurrentlyUnhandled();
let runner;
// Override process.exit with an undetectable replacement
// to report when it is called from a test (which it should never be).
const {apply} = Reflect;
const realExit = process.exit;
async function exit(code, forceSync = false) {
dependencyTracking.flush();
const flushing = channel.flush();
if (!forceSync) {
await flushing;
}
apply(realExit, process, [code]);
}
const handleProcessExit = (fn, receiver, args) => {
const error = new Error('Unexpected process.exit()');
Error.captureStackTrace(error, handleProcessExit);
const {stack} = serializeError('', true, error);
channel.send({type: 'process-exit', stack});
// Make sure to extract the code only from `args` rather than e.g. `Array.prototype`.
// This level of paranoia is usually unwarranted, but we're dealing with test code
// that has already colored outside the lines.
const code = args.length > 0 ? args[0] : undefined;
// Force a synchronous exit as guaranteed by the real process.exit().
exit(code, true);
};
process.exit = new Proxy(realExit, {
apply: handleProcessExit,
});
const run = async options => {
setOptions(options);
setChalk(options.chalkOptions);
if (options.chalkOptions.level > 0) {
const {stdout, stderr} = process;
global.console = Object.assign(global.console, new console.Console({stdout, stderr, colorMode: true}));
}
let checkSelectedByLineNumbers;
try {
checkSelectedByLineNumbers = lineNumberSelection({
file: options.file,
lineNumbers: options.lineNumbers,
});
} catch (error) {
channel.send({type: 'line-number-selection-error', err: serializeError('Line number selection error', false, error, options.file)});
checkSelectedByLineNumbers = () => false;
}
runner = new Runner({
checkSelectedByLineNumbers,
experiments: options.experiments,
failFast: options.failFast,
failWithoutAssertions: options.failWithoutAssertions,
file: options.file,
match: options.match,
projectDir: options.projectDir,
recordNewSnapshots: options.recordNewSnapshots,
runOnlyExclusive: options.runOnlyExclusive,
serial: options.serial,
snapshotDir: options.snapshotDir,
updateSnapshots: options.updateSnapshots,
});
refs.runnerChain = runner.chain;
channel.peerFailed.then(() => {
runner.interrupt();
});
runner.on('dependency', dependencyTracking.track);
runner.on('stateChange', state => channel.send(state));
runner.on('error', error => {
channel.send({type: 'internal-error', err: serializeError('Internal runner error', false, error, runner.file)});
exit(1);
});
runner.on('finish', async () => {
try {
const {touchedFiles} = await runner.saveSnapshotState();
if (touchedFiles) {
channel.send({type: 'touched-files', files: touchedFiles});
}
} catch (error) {
channel.send({type: 'internal-error', err: serializeError('Internal runner error', false, error, runner.file)});
exit(1);
return;
}
try {
await Promise.all(sharedWorkerTeardowns.map(fn => fn()));
} catch (error) {
channel.send({type: 'uncaught-exception', err: serializeError('Shared worker teardown error', false, error, runner.file)});
exit(1);
return;
}
nowAndTimers.setImmediate(() => {
for (const rejection of currentlyUnhandled()) {
channel.send({type: 'unhandled-rejection', err: serializeError('Unhandled rejection', true, rejection.reason, runner.file)});
}
exit(0);
});
});
process.on('uncaughtException', error => {
channel.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)});
exit(1);
});
// Store value to prevent required modules from modifying it.
const testPath = options.file;
const extensionsToLoadAsModules = Object.entries(options.moduleTypes)
.filter(([, type]) => type === 'module')
.map(([extension]) => extension);
// Install before processing options.require, so if helpers are added to the
// require configuration the *compiled* helper will be loaded.
const {projectDir, providerStates = []} = options;
const providers = [];
await Promise.all(providerStates.map(async ({type, state}) => {
if (type === 'typescript') {
const provider = await providerManager.typescript(projectDir);
providers.push(provider.worker({extensionsToLoadAsModules, state}));
}
}));
const require = createRequire(import.meta.url);
const load = async ref => {
for (const provider of providers) {
if (provider.canLoad(ref)) {
return provider.load(ref, {requireFn: require});
}
}
for (const extension of extensionsToLoadAsModules) {
if (ref.endsWith(`.${extension}`)) {
return import(pathToFileURL(ref));
}
}
// We still support require() since it's more easily monkey-patched.
return require(ref);
};
try {
for await (const ref of (options.require || [])) {
await load(ref);
}
// Install dependency tracker after the require configuration has been evaluated
// to make sure we also track dependencies with custom require hooks
dependencyTracking.install(require.extensions, testPath);
if (options.debug && options.debug.port !== undefined && options.debug.host !== undefined) {
// If an inspector was active when the main process started, and is
// already active for the worker process, do not open a new one.
const {default: inspector} = await import('node:inspector');
if (!options.debug.active || inspector.url() === undefined) {
inspector.open(options.debug.port, options.debug.host, true);
}
if (options.debug.break) {
debugger; // eslint-disable-line no-debugger
}
}
await load(testPath);
if (flags.loadedMain) {
// Unreference the channel if the test file required AVA. This stops it
// from keeping the event loop busy, which means the `beforeExit` event can be
// used to detect when tests stall.
channel.unref();
} else {
channel.send({type: 'missing-ava-import'});
exit(1);
}
} catch (error) {
channel.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)});
exit(1);
}
};
const onError = error => {
// There shouldn't be any errors, but if there are we may not have managed
// to bootstrap enough code to serialize them. Re-throw and let the process
// crash.
setImmediate(() => {
throw error;
});
};
let options;
if (isRunningInThread) {
channel.send({type: 'starting'}); // AVA won't terminate the worker thread until it's seen this message.
({options} = workerData);
delete workerData.options; // Don't allow user code access.
} else if (isRunningInChildProcess) {
channel.send({type: 'ready-for-options'});
options = await channel.options;
}
try {
await run(options);
} catch (error) {
onError(error);
}

View File

@@ -0,0 +1,290 @@
'use strict';
const events = require('node:events');
const process = require('node:process');
const {MessageChannel, threadId} = require('node:worker_threads');
const timers = require('../now-and-timers.cjs');
const {isRunningInChildProcess, isRunningInThread} = require('./utils.cjs');
let pEvent = async (emitter, event, options) => {
// We need to import p-event, but import() is asynchronous. Buffer any events
// emitted in the meantime. Don't handle errors.
const buffer = [];
const addToBuffer = (...args) => buffer.push(args);
emitter.on(event, addToBuffer);
try {
({pEvent} = await import('p-event'));
} finally {
emitter.off(event, addToBuffer);
}
if (buffer.length === 0) {
return pEvent(emitter, event, options);
}
// Now replay buffered events.
const replayEmitter = new events.EventEmitter();
const promise = pEvent(replayEmitter, event, options);
for (const args of buffer) {
replayEmitter.emit(event, ...args);
}
const replay = (...args) => replayEmitter.emit(event, ...args);
emitter.on(event, replay);
try {
return await promise;
} finally {
emitter.off(event, replay);
}
};
const selectAvaMessage = type => message => message.ava && message.ava.type === type;
class RefCounter {
constructor() {
this.count = 0;
}
refAndTest() {
return ++this.count === 1;
}
testAndUnref() {
return this.count > 0 && --this.count === 0;
}
}
class MessagePortHandle {
constructor(port) {
this.counter = new RefCounter();
this.unreferenceable = false;
this.channel = port;
// Referencing the port does not immediately prevent the thread from
// exiting. Use a timer to keep a reference for at least a second.
this.workaroundTimer = timers.setTimeout(() => {}, 1000).unref();
}
forceUnref() {
if (this.unreferenceable) {
return;
}
this.unreferenceable = true;
this.workaroundTimer.unref();
this.channel.unref();
}
ref() {
if (!this.unreferenceable && this.counter.refAndTest()) {
this.workaroundTimer.refresh().ref();
this.channel.ref();
}
}
unref() {
if (!this.unreferenceable && this.counter.testAndUnref()) {
this.workaroundTimer.unref();
this.channel.unref();
}
}
send(evt, transferList) {
this.channel.postMessage({ava: evt}, transferList);
}
}
class IpcHandle {
constructor(bufferedSend) {
this.counter = new RefCounter();
this.channel = process;
this.sendRaw = bufferedSend;
}
ref() {
if (this.counter.refAndTest()) {
process.channel.ref();
}
}
unref() {
if (this.counter.testAndUnref()) {
process.channel.unref();
}
}
send(evt) {
this.sendRaw({ava: evt});
}
}
let handle;
if (isRunningInChildProcess) {
const {controlFlow} = require('../ipc-flow-control.cjs');
handle = new IpcHandle(controlFlow(process));
} else if (isRunningInThread) {
const {parentPort} = require('node:worker_threads');
handle = new MessagePortHandle(parentPort);
}
// The attaching of message listeners will cause the port to be referenced by
// Node.js. In order to keep track, explicitly reference before attaching.
handle.ref();
exports.options = pEvent(handle.channel, 'message', selectAvaMessage('options')).then(message => message.ava.options); // eslint-disable-line unicorn/prefer-top-level-await
exports.peerFailed = pEvent(handle.channel, 'message', selectAvaMessage('peer-failed'));
exports.send = handle.send.bind(handle);
exports.unref = handle.unref.bind(handle);
let pendingPings = Promise.resolve();
async function flush() {
handle.ref();
const promise = pendingPings.then(async () => {
handle.send({type: 'ping'});
await pEvent(handle.channel, 'message', selectAvaMessage('pong'));
if (promise === pendingPings) {
handle.unref();
}
});
pendingPings = promise;
await promise;
}
exports.flush = flush;
let channelCounter = 0;
let messageCounter = 0;
const channelEmitters = new Map();
function createChannelEmitter(channelId) {
if (channelEmitters.size === 0) {
handle.channel.on('message', message => {
if (!message.ava) {
return;
}
const {channelId, type, ...payload} = message.ava;
if (type === 'shared-worker-error') {
const emitter = channelEmitters.get(channelId);
if (emitter !== undefined) {
emitter.emit(type, payload);
}
}
});
}
const emitter = new events.EventEmitter();
channelEmitters.set(channelId, emitter);
return [emitter, () => channelEmitters.delete(channelId)];
}
function registerSharedWorker(filename, initialData) {
const channelId = `${threadId}/channel/${++channelCounter}`;
const {port1: ourPort, port2: theirPort} = new MessageChannel();
const sharedWorkerHandle = new MessagePortHandle(ourPort);
const [channelEmitter, unsubscribe] = createChannelEmitter(channelId);
handle.send({
type: 'shared-worker-connect',
channelId,
filename,
initialData,
port: theirPort,
}, [theirPort]);
let currentlyAvailable = false;
let error = null;
// The attaching of message listeners will cause the port to be referenced by
// Node.js. In order to keep track, explicitly reference before attaching.
sharedWorkerHandle.ref();
const ready = pEvent(ourPort, 'message', ({type}) => type === 'ready').then(() => {
currentlyAvailable = error === null;
}).finally(() => {
// Once ready, it's up to user code to subscribe to messages, which (see
// below) causes us to reference the port.
sharedWorkerHandle.unref();
});
const messageEmitters = new Set();
// Errors are received over the test worker channel, not the message port
// dedicated to the shared worker.
pEvent(channelEmitter, 'shared-worker-error').then(() => {
unsubscribe();
sharedWorkerHandle.forceUnref();
error = new Error('The shared worker is no longer available');
currentlyAvailable = false;
for (const emitter of messageEmitters) {
emitter.emit('error', error);
}
});
ourPort.on('message', message => {
if (message.type === 'message') {
// Wait for a turn of the event loop, to allow new subscriptions to be set
// up in response to the previous message.
setImmediate(() => {
for (const emitter of messageEmitters) {
emitter.emit('message', message);
}
});
}
});
return {
forceUnref: () => sharedWorkerHandle.forceUnref(),
ready,
channel: {
available: ready,
get currentlyAvailable() {
return currentlyAvailable;
},
async * receive() {
if (error !== null) {
throw error;
}
const emitter = new events.EventEmitter();
messageEmitters.add(emitter);
try {
sharedWorkerHandle.ref();
for await (const [message] of events.on(emitter, 'message')) {
yield message;
}
} finally {
sharedWorkerHandle.unref();
messageEmitters.delete(emitter);
}
},
post(data, replyTo) {
if (error !== null) {
throw error;
}
if (!currentlyAvailable) {
throw new Error('Shared worker is not yet available');
}
const messageId = `${channelId}/message/${++messageCounter}`;
ourPort.postMessage({
type: 'message',
messageId,
replyTo,
data,
});
return messageId;
},
},
};
}
exports.registerSharedWorker = registerSharedWorker;

View File

@@ -0,0 +1,48 @@
import process from 'node:process';
import channel from './channel.cjs';
const seenDependencies = new Set();
let newDependencies = [];
function flush() {
if (newDependencies.length === 0) {
return;
}
channel.send({type: 'dependencies', dependencies: newDependencies});
newDependencies = [];
}
function track(filename) {
if (seenDependencies.has(filename)) {
return;
}
if (newDependencies.length === 0) {
process.nextTick(flush);
}
seenDependencies.add(filename);
newDependencies.push(filename);
}
const tracker = {
flush,
track,
install(extensions, testPath) {
for (const ext of Object.keys(extensions)) {
const wrappedHandler = extensions[ext];
extensions[ext] = (module, filename) => {
if (filename !== testPath) {
track(filename);
}
wrappedHandler(module, filename);
};
}
},
};
export default tracker;

View File

@@ -0,0 +1,19 @@
'use strict';
const path = require('node:path');
const process = require('node:process');
const {isRunningInThread, isRunningInChildProcess} = require('./utils.cjs');
// Check if the test is being run without AVA cli
if (!isRunningInChildProcess && !isRunningInThread) {
if (process.argv[1]) {
const fp = path.relative('.', process.argv[1]);
console.log();
console.error(`Test files must be run with the AVA CLI:\n\n $ ava ${fp}\n`);
process.exit(1); // eslint-disable-line unicorn/no-process-exit
} else {
throw new Error('The ava module can only be imported in test files');
}
}

View File

@@ -0,0 +1,141 @@
import * as fs from 'node:fs';
import {createRequire, findSourceMap} from 'node:module';
import {pathToFileURL} from 'node:url';
import callsites from 'callsites';
const require = createRequire(import.meta.url);
function parse(file) {
// Avoid loading these until we actually need to select tests by line number.
const acorn = require('acorn');
const walk = require('acorn-walk');
const ast = acorn.parse(fs.readFileSync(file, 'utf8'), {
ecmaVersion: 'latest',
locations: true,
sourceType: 'module',
});
const locations = [];
walk.simple(ast, {
CallExpression(node) {
locations.push(node.loc);
},
});
// Walking is depth-first, but we want to sort these breadth-first.
locations.sort((a, b) => {
if (a.start.line === b.start.line) {
return a.start.column - b.start.column;
}
return a.start.line - b.start.line;
});
return locations;
}
function findTest(locations, declaration) {
// Find all calls that span the test declaration.
const spans = locations.filter(loc => {
if (loc.start.line > declaration.line || loc.end.line < declaration.line) {
return false;
}
if (loc.start.line === declaration.line && loc.start.column > declaration.column) {
return false;
}
if (loc.end.line === declaration.line && loc.end.column < declaration.column) {
return false;
}
return true;
});
// Locations should be sorted by source order, so the last span must be the test.
return spans.pop();
}
const range = (start, end) => Array.from({length: end - start + 1}).fill(start).map((element, index) => element + index);
const translate = (sourceMap, pos) => {
if (sourceMap === null) {
return pos;
}
const entry = sourceMap.findEntry(pos.line - 1, pos.column); // Source maps are 0-based
// When used with ts-node/register, we've seen entries without original values. Return the
// original position.
if (entry.originalLine === undefined || entry.originalColumn === undefined) {
return pos;
}
return {
line: entry.originalLine + 1, // Readjust for Acorn.
column: entry.originalColumn,
};
};
export default function lineNumberSelection({file, lineNumbers = []}) {
if (lineNumbers.length === 0) {
return undefined;
}
const selected = new Set(lineNumbers);
let locations = parse(file);
let lookedForSourceMap = false;
let sourceMap = null;
return () => {
if (!lookedForSourceMap) {
lookedForSourceMap = true;
// The returned function is called *after* the file has been loaded.
// Source maps are not available before then.
sourceMap = findSourceMap(file);
if (sourceMap === undefined) {
// Prior to Node.js 18.8.0, the value when a source map could not be found was `undefined`.
// This changed to `null` in <https://github.com/nodejs/node/pull/43875>.
sourceMap = null;
}
if (sourceMap !== null) {
locations = locations.map(({start, end}) => ({
start: translate(sourceMap, start),
end: translate(sourceMap, end),
}));
}
}
// Assume this is called from a test declaration, which is located in the file.
// If not… don't select the test!
const callSite = callsites().find(callSite => {
const current = callSite.getFileName();
if (file.startsWith('file://')) {
return current.startsWith('file://') ? file === current : file === pathToFileURL(current).toString();
}
return current.startsWith('file://') ? pathToFileURL(file).toString() === current : file === current;
});
if (!callSite) {
return false;
}
const start = translate(sourceMap, {
line: callSite.getLineNumber(), // 1-based
column: callSite.getColumnNumber() - 1, // Comes out as 1-based, Acorn wants 0-based
});
const test = findTest(locations, start);
if (!test) {
return false;
}
return range(test.start.line, test.end.line).some(line => selected.has(line));
};
}

View File

@@ -0,0 +1,12 @@
'use strict';
require('./guard-environment.cjs'); // eslint-disable-line import/no-unassigned-import
const assert = require('node:assert');
const {flags, refs} = require('./state.cjs');
assert(refs.runnerChain);
flags.loadedMain = true;
module.exports = refs.runnerChain;

View File

@@ -0,0 +1,17 @@
'use strict';
let options = null;
exports.get = () => {
if (!options) {
throw new Error('Options have not yet been set');
}
return options;
};
exports.set = newOptions => {
if (options) {
throw new Error('Options have already been set');
}
options = newOptions;
};

View File

@@ -0,0 +1,130 @@
const pkg = require('../../package.json');
const {registerSharedWorker: register} = require('./channel.cjs');
const options = require('./options.cjs');
const {sharedWorkerTeardowns, waitForReady} = require('./state.cjs');
require('./guard-environment.cjs'); // eslint-disable-line import/no-unassigned-import
const workers = new Map();
const workerTeardownFns = new WeakMap();
function createSharedWorker(filename, initialData, teardown) {
const {channel, forceUnref, ready} = register(filename, initialData, teardown);
waitForReady.push(ready);
sharedWorkerTeardowns.push(async () => {
try {
await teardown();
} finally {
forceUnref();
}
});
class ReceivedMessage {
constructor(id, data) {
this.id = id;
this.data = data;
}
reply(data) {
return publishMessage(data, this.id);
}
}
// Ensure that, no matter how often it's received, we have a stable message
// object.
const messageCache = new WeakMap();
async function * receiveMessages(replyTo) {
for await (const evt of channel.receive()) {
if (replyTo === undefined && evt.replyTo !== undefined) {
continue;
}
if (replyTo !== undefined && evt.replyTo !== replyTo) {
continue;
}
let message = messageCache.get(evt);
if (message === undefined) {
message = new ReceivedMessage(evt.messageId, evt.data);
messageCache.set(evt, message);
}
yield message;
}
}
function publishMessage(data, replyTo) {
const id = channel.post(data, replyTo);
return {
id,
async * replies() {
yield * receiveMessages(id);
},
};
}
return {
available: channel.available,
protocol: 'ava-4',
get currentlyAvailable() {
return channel.currentlyAvailable;
},
publish(data) {
return publishMessage(data);
},
async * subscribe() {
yield * receiveMessages();
},
};
}
function registerSharedWorker({
filename,
initialData,
supportedProtocols,
teardown,
}) {
const options_ = options.get();
if (!options_.workerThreads) {
throw new Error('Shared workers can be used only when worker threads are enabled');
}
if (!supportedProtocols.includes('ava-4')) {
throw new Error(`This version of AVA (${pkg.version}) does not support any of the desired shared worker protocols: ${supportedProtocols.join(',')}`);
}
filename = String(filename); // Allow URL instances.
let worker = workers.get(filename);
if (worker === undefined) {
worker = createSharedWorker(filename, initialData, async () => {
// Run possibly asynchronous teardown functions serially, in reverse
// order. Any error will crash the worker.
const teardownFns = workerTeardownFns.get(worker);
if (teardownFns !== undefined) {
for await (const fn of [...teardownFns].reverse()) {
await fn();
}
}
});
workers.set(filename, worker);
}
if (teardown !== undefined) {
if (workerTeardownFns.has(worker)) {
workerTeardownFns.get(worker).push(teardown);
} else {
workerTeardownFns.set(worker, [teardown]);
}
}
return worker;
}
exports.registerSharedWorker = registerSharedWorker;

View File

@@ -0,0 +1,5 @@
'use strict';
exports.flags = {loadedMain: false};
exports.refs = {runnerChain: null};
exports.sharedWorkerTeardowns = [];
exports.waitForReady = [];

View File

@@ -0,0 +1,6 @@
'use strict';
const process = require('node:process');
const {isMainThread} = require('node:worker_threads');
exports.isRunningInThread = isMainThread === false;
exports.isRunningInChildProcess = typeof process.send === 'function';

9
github/codeql-action-v2/node_modules/ava/license generated vendored Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
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.

View File

@@ -0,0 +1,33 @@
export interface Options {
/**
Match only the first ANSI escape.
@default false
*/
readonly onlyFirst: boolean;
}
/**
Regular expression for matching ANSI escape codes.
@example
```
import ansiRegex from 'ansi-regex';
ansiRegex().test('\u001B[4mcake\u001B[0m');
//=> true
ansiRegex().test('cake');
//=> false
'\u001B[4mcake\u001B[0m'.match(ansiRegex());
//=> ['\u001B[4m', '\u001B[0m']
'\u001B[4mcake\u001B[0m'.match(ansiRegex({onlyFirst: true}));
//=> ['\u001B[4m']
'\u001B]8;;https://github.com\u0007click\u001B]8;;\u0007'.match(ansiRegex());
//=> ['\u001B]8;;https://github.com\u0007', '\u001B]8;;\u0007']
```
*/
export default function ansiRegex(options?: Options): RegExp;

View File

@@ -0,0 +1,8 @@
export default function ansiRegex({onlyFirst = false} = {}) {
const pattern = [
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
].join('|');
return new RegExp(pattern, onlyFirst ? undefined : 'g');
}

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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.

View File

@@ -0,0 +1,58 @@
{
"name": "ansi-regex",
"version": "6.0.1",
"description": "Regular expression for matching ANSI escape codes",
"license": "MIT",
"repository": "chalk/ansi-regex",
"funding": "https://github.com/chalk/ansi-regex?sponsor=1",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=12"
},
"scripts": {
"test": "xo && ava && tsd",
"view-supported": "node fixtures/view-codes.js"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"cli",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"command-line",
"text",
"regex",
"regexp",
"re",
"match",
"test",
"find",
"pattern"
],
"devDependencies": {
"ava": "^3.15.0",
"tsd": "^0.14.0",
"xo": "^0.38.2"
}
}

View File

@@ -0,0 +1,72 @@
# ansi-regex
> Regular expression for matching [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code)
## Install
```
$ npm install ansi-regex
```
## Usage
```js
import ansiRegex from 'ansi-regex';
ansiRegex().test('\u001B[4mcake\u001B[0m');
//=> true
ansiRegex().test('cake');
//=> false
'\u001B[4mcake\u001B[0m'.match(ansiRegex());
//=> ['\u001B[4m', '\u001B[0m']
'\u001B[4mcake\u001B[0m'.match(ansiRegex({onlyFirst: true}));
//=> ['\u001B[4m']
'\u001B]8;;https://github.com\u0007click\u001B]8;;\u0007'.match(ansiRegex());
//=> ['\u001B]8;;https://github.com\u0007', '\u001B]8;;\u0007']
```
## API
### ansiRegex(options?)
Returns a regex for matching ANSI escape codes.
#### options
Type: `object`
##### onlyFirst
Type: `boolean`\
Default: `false` *(Matches any ANSI escape codes in a string)*
Match only the first ANSI escape.
## FAQ
### Why do you test for codes not in the ECMA 48 standard?
Some of the codes we run as a test are codes that we acquired finding various lists of non-standard or manufacturer specific codes. We test for both standard and non-standard codes, as most of them follow the same or similar format and can be safely matched in strings without the risk of removing actual string content. There are a few non-standard control codes that do not follow the traditional format (i.e. they end in numbers) thus forcing us to exclude them from the test because we cannot reliably match them.
On the historical side, those ECMA standards were established in the early 90's whereas the VT100, for example, was designed in the mid/late 70's. At that point in time, control codes were still pretty ungoverned and engineers used them for a multitude of things, namely to activate hardware ports that may have been proprietary. Somewhere else you see a similar 'anarchy' of codes is in the x86 architecture for processors; there are a ton of "interrupts" that can mean different things on certain brands of processors, most of which have been phased out.
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-ansi-regex?utm_source=npm-ansi-regex&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>

View File

@@ -0,0 +1,36 @@
/**
Convert a value to an array.
_Specifying `null` or `undefined` results in an empty array._
@example
```
import arrify from 'arrify';
arrify('🦄');
//=> ['🦄']
arrify(['🦄']);
//=> ['🦄']
arrify(new Set(['🦄']));
//=> ['🦄']
arrify(null);
//=> []
arrify(undefined);
//=> []
```
*/
export default function arrify<ValueType>(
value: ValueType
): ValueType extends (null | undefined)
? [] // eslint-disable-line @typescript-eslint/ban-types
: ValueType extends string
? [string]
: ValueType extends readonly unknown[]
? ValueType
: ValueType extends Iterable<infer T>
? T[]
: [ValueType];

View File

@@ -0,0 +1,19 @@
export default function arrify(value) {
if (value === null || value === undefined) {
return [];
}
if (Array.isArray(value)) {
return value;
}
if (typeof value === 'string') {
return [value];
}
if (typeof value[Symbol.iterator] === 'function') {
return [...value];
}
return [value];
}

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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.

View File

@@ -0,0 +1,38 @@
{
"name": "arrify",
"version": "3.0.0",
"description": "Convert a value to an array",
"license": "MIT",
"repository": "sindresorhus/arrify",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=12"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"array",
"arrify",
"arrayify",
"convert",
"value",
"ensure"
],
"devDependencies": {
"ava": "^3.15.0",
"tsd": "^0.14.0",
"xo": "^0.39.1"
}
}

View File

@@ -0,0 +1,44 @@
# arrify
> Convert a value to an array
## Install
```
$ npm install arrify
```
## Usage
```js
import arrify from 'arrify';
arrify('🦄');
//=> ['🦄']
arrify(['🦄']);
//=> ['🦄']
arrify(new Set(['🦄']));
//=> ['🦄']
arrify(null);
//=> []
arrify(undefined);
//=> []
```
*Specifying `null` or `undefined` results in an empty array.*
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-arrify?utm_source=npm-arrify&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>

View File

@@ -0,0 +1,87 @@
type AnyFunction = (...args: any[]) => any;
export interface CallSite {
/**
Returns the value of `this`.
*/
getThis(): unknown | undefined;
/**
Returns the type of `this` as a string. This is the name of the function stored in the constructor field of `this`, if available, otherwise the object's `[[Class]]` internal property.
*/
getTypeName(): string | null;
/**
Returns the current function.
*/
getFunction(): AnyFunction | undefined;
/**
Returns the name of the current function, typically its `name` property. If a name property is not available an attempt will be made to try to infer a name from the function's context.
*/
getFunctionName(): string | null;
/**
Returns the name of the property of `this` or one of its prototypes that holds the current function.
*/
getMethodName(): string | undefined;
/**
Returns the name of the script if this function was defined in a script.
*/
getFileName(): string | null;
/**
Returns the current line number if this function was defined in a script.
*/
getLineNumber(): number | null;
/**
Returns the current column number if this function was defined in a script.
*/
getColumnNumber(): number | null;
/**
Returns a string representing the location where `eval` was called if this function was created using a call to `eval`.
*/
getEvalOrigin(): string | undefined;
/**
Returns `true` if this is a top-level invocation, that is, if it's a global object.
*/
isToplevel(): boolean;
/**
Returns `true` if this call takes place in code defined by a call to `eval`.
*/
isEval(): boolean;
/**
Returns `true` if this call is in native V8 code.
*/
isNative(): boolean;
/**
Returns `true` if this is a constructor call.
*/
isConstructor(): boolean;
}
/**
Get callsites from the V8 stack trace API.
@returns An array of `CallSite` objects.
@example
```
import callsites from 'callsites';
function unicorn() {
console.log(callsites()[0].getFileName());
//=> '/Users/sindresorhus/dev/callsites/test.js'
}
unicorn();
```
*/
export default function callsites(): CallSite[];

View File

@@ -0,0 +1,7 @@
export default function callsites() {
const _prepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (_, stack) => stack;
const stack = new Error().stack.slice(1); // eslint-disable-line unicorn/error-message
Error.prepareStackTrace = _prepareStackTrace;
return stack;
}

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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.

View File

@@ -0,0 +1,42 @@
{
"name": "callsites",
"version": "4.0.0",
"description": "Get callsites from the V8 stack trace API",
"license": "MIT",
"repository": "sindresorhus/callsites",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=12.20"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"stacktrace",
"v8",
"callsite",
"callsites",
"stack",
"trace",
"function",
"file",
"line",
"debug"
],
"devDependencies": {
"ava": "^3.15.0",
"tsd": "^0.17.0",
"xo": "^0.44.0"
}
}

View File

@@ -0,0 +1,52 @@
# callsites
> Get callsites from the [V8 stack trace API](https://v8.dev/docs/stack-trace-api)
## Install
```
$ npm install callsites
```
## Usage
```js
import callsites from 'callsites';
function unicorn() {
console.log(callsites()[0].getFileName());
//=> '/Users/sindresorhus/dev/callsites/test.js'
}
unicorn();
```
## API
Returns an array of callsite objects with the following methods:
- `getThis`: Returns the value of `this`.
- `getTypeName`: Returns the type of `this` as a string. This is the name of the function stored in the constructor field of `this`, if available, otherwise the object's `[[Class]]` internal property.
- `getFunction`: Returns the current function.
- `getFunctionName`: Returns the name of the current function, typically its `name` property. If a name property is not available an attempt will be made to try to infer a name from the function's context.
- `getMethodName`: Returns the name of the property of `this` or one of its prototypes that holds the current function.
- `getFileName`: If this function was defined in a script returns the name of the script.
- `getLineNumber`: If this function was defined in a script returns the current line number.
- `getColumnNumber`: If this function was defined in a script returns the current column number
- `getEvalOrigin`: If this function was created using a call to `eval` returns a string representing the location where `eval` was called.
- `isToplevel`: Is this a top-level invocation, that is, is this the global object?
- `isEval`: Does this call take place in code defined by a call to `eval`?
- `isNative`: Is this call in native V8 code?
- `isConstructor`: Is this a constructor call?
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-callsites?utm_source=npm-callsites&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>

View File

@@ -0,0 +1,94 @@
import process from 'node:process';
import fs from 'node:fs';
import path from 'node:path';
import fastGlob from 'fast-glob';
import gitIgnore from 'ignore';
import slash from 'slash';
import {toPath, isNegativePattern} from './utilities.js';
const ignoreFilesGlobOptions = {
ignore: [
'**/node_modules',
'**/flow-typed',
'**/coverage',
'**/.git',
],
absolute: true,
dot: true,
};
export const GITIGNORE_FILES_PATTERN = '**/.gitignore';
const applyBaseToPattern = (pattern, base) => isNegativePattern(pattern)
? '!' + path.posix.join(base, pattern.slice(1))
: path.posix.join(base, pattern);
const parseIgnoreFile = (file, cwd) => {
const base = slash(path.relative(cwd, path.dirname(file.filePath)));
return file.content
.split(/\r?\n/)
.filter(line => line && !line.startsWith('#'))
.map(pattern => applyBaseToPattern(pattern, base));
};
const toRelativePath = (fileOrDirectory, cwd) => {
cwd = slash(cwd);
if (path.isAbsolute(fileOrDirectory)) {
if (slash(fileOrDirectory).startsWith(cwd)) {
return path.relative(cwd, fileOrDirectory);
}
throw new Error(`Path ${fileOrDirectory} is not in cwd ${cwd}`);
}
return fileOrDirectory;
};
const getIsIgnoredPredicate = (files, cwd) => {
const patterns = files.flatMap(file => parseIgnoreFile(file, cwd));
const ignores = gitIgnore().add(patterns);
return fileOrDirectory => {
fileOrDirectory = toPath(fileOrDirectory);
fileOrDirectory = toRelativePath(fileOrDirectory, cwd);
return fileOrDirectory ? ignores.ignores(slash(fileOrDirectory)) : false;
};
};
const normalizeOptions = (options = {}) => ({
cwd: toPath(options.cwd) || process.cwd(),
suppressErrors: Boolean(options.suppressErrors),
deep: typeof options.deep === 'number' ? options.deep : Number.POSITIVE_INFINITY,
});
export const isIgnoredByIgnoreFiles = async (patterns, options) => {
const {cwd, suppressErrors, deep} = normalizeOptions(options);
const paths = await fastGlob(patterns, {cwd, suppressErrors, deep, ...ignoreFilesGlobOptions});
const files = await Promise.all(
paths.map(async filePath => ({
filePath,
content: await fs.promises.readFile(filePath, 'utf8'),
})),
);
return getIsIgnoredPredicate(files, cwd);
};
export const isIgnoredByIgnoreFilesSync = (patterns, options) => {
const {cwd, suppressErrors, deep} = normalizeOptions(options);
const paths = fastGlob.sync(patterns, {cwd, suppressErrors, deep, ...ignoreFilesGlobOptions});
const files = paths.map(filePath => ({
filePath,
content: fs.readFileSync(filePath, 'utf8'),
}));
return getIsIgnoredPredicate(files, cwd);
};
export const isGitIgnored = options => isIgnoredByIgnoreFiles(GITIGNORE_FILES_PATTERN, options);
export const isGitIgnoredSync = options => isIgnoredByIgnoreFilesSync(GITIGNORE_FILES_PATTERN, options);

View File

@@ -0,0 +1,205 @@
import {Options as FastGlobOptions, Entry} from 'fast-glob';
export type GlobEntry = Entry;
export interface GlobTask {
readonly patterns: string[];
readonly options: Options;
}
export type ExpandDirectoriesOption =
| boolean
| readonly string[]
| {files?: readonly string[]; extensions?: readonly string[]};
type FastGlobOptionsWithoutCwd = Omit<FastGlobOptions, 'cwd'>;
export interface Options extends FastGlobOptionsWithoutCwd {
/**
If set to `true`, `globby` will automatically glob directories for you. If you define an `Array` it will only glob files that matches the patterns inside the `Array`. You can also define an `Object` with `files` and `extensions` like in the example below.
Note that if you set this option to `false`, you won't get back matched directories unless you set `onlyFiles: false`.
@default true
@example
```
import {globby} from 'globby';
const paths = await globby('images', {
expandDirectories: {
files: ['cat', 'unicorn', '*.jpg'],
extensions: ['png']
}
});
console.log(paths);
//=> ['cat.png', 'unicorn.png', 'cow.jpg', 'rainbow.jpg']
```
*/
readonly expandDirectories?: ExpandDirectoriesOption;
/**
Respect ignore patterns in `.gitignore` files that apply to the globbed files.
@default false
*/
readonly gitignore?: boolean;
/**
Glob patterns to look for ignore files, which are then used to ignore globbed files.
This is a more generic form of the `gitignore` option, allowing you to find ignore files with a [compatible syntax](http://git-scm.com/docs/gitignore). For instance, this works with Babel's `.babelignore`, Prettier's `.prettierignore`, or ESLint's `.eslintignore` files.
@default undefined
*/
readonly ignoreFiles?: string | readonly string[];
/**
The current working directory in which to search.
@default process.cwd()
*/
readonly cwd?: URL | string;
}
export interface GitignoreOptions {
readonly cwd?: URL | string;
}
export type GlobbyFilterFunction = (path: URL | string) => boolean;
/**
Find files and directories using glob patterns.
Note that glob patterns can only contain forward-slashes, not backward-slashes, so if you want to construct a glob pattern from path components, you need to use `path.posix.join()` instead of `path.join()`.
@param patterns - See the supported [glob patterns](https://github.com/sindresorhus/globby#globbing-patterns).
@param options - See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3) in addition to the ones in this package.
@returns The matching paths.
@example
```
import {globby} from 'globby';
const paths = await globby(['*', '!cake']);
console.log(paths);
//=> ['unicorn', 'rainbow']
```
*/
export function globby(
patterns: string | readonly string[],
options: Options & {objectMode: true}
): Promise<GlobEntry[]>;
export function globby(
patterns: string | readonly string[],
options?: Options
): Promise<string[]>;
/**
Find files and directories using glob patterns.
Note that glob patterns can only contain forward-slashes, not backward-slashes, so if you want to construct a glob pattern from path components, you need to use `path.posix.join()` instead of `path.join()`.
@param patterns - See the supported [glob patterns](https://github.com/sindresorhus/globby#globbing-patterns).
@param options - See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3) in addition to the ones in this package.
@returns The matching paths.
*/
export function globbySync(
patterns: string | readonly string[],
options: Options & {objectMode: true}
): GlobEntry[];
export function globbySync(
patterns: string | readonly string[],
options?: Options
): string[];
/**
Find files and directories using glob patterns.
Note that glob patterns can only contain forward-slashes, not backward-slashes, so if you want to construct a glob pattern from path components, you need to use `path.posix.join()` instead of `path.join()`.
@param patterns - See the supported [glob patterns](https://github.com/sindresorhus/globby#globbing-patterns).
@param options - See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3) in addition to the ones in this package.
@returns The stream of matching paths.
@example
```
import {globbyStream} from 'globby';
for await (const path of globbyStream('*.tmp')) {
console.log(path);
}
```
*/
export function globbyStream(
patterns: string | readonly string[],
options?: Options
): NodeJS.ReadableStream;
/**
Note that you should avoid running the same tasks multiple times as they contain a file system cache. Instead, run this method each time to ensure file system changes are taken into consideration.
@param patterns - See the supported [glob patterns](https://github.com/sindresorhus/globby#globbing-patterns).
@param options - See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3) in addition to the ones in this package.
@returns An object in the format `{pattern: string, options: object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages.
*/
export function generateGlobTasks(
patterns: string | readonly string[],
options?: Options
): Promise<GlobTask[]>;
/**
@see generateGlobTasks
@returns An object in the format `{pattern: string, options: object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages.
*/
export function generateGlobTasksSync(
patterns: string | readonly string[],
options?: Options
): GlobTask[];
/**
Note that the options affect the results.
This function is backed by [`fast-glob`](https://github.com/mrmlnc/fast-glob#isdynamicpatternpattern-options).
@param patterns - See the supported [glob patterns](https://github.com/sindresorhus/globby#globbing-patterns).
@param options - See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3).
@returns Whether there are any special glob characters in the `patterns`.
*/
export function isDynamicPattern(
patterns: string | readonly string[],
options?: FastGlobOptionsWithoutCwd & {
/**
The current working directory in which to search.
@default process.cwd()
*/
readonly cwd?: URL | string;
}
): boolean;
/**
`.gitignore` files matched by the ignore config are not used for the resulting filter function.
@returns A filter function indicating whether a given path is ignored via a `.gitignore` file.
@example
```
import {isGitIgnored} from 'globby';
const isIgnored = await isGitIgnored();
console.log(isIgnored('some/file'));
```
*/
export function isGitIgnored(options?: GitignoreOptions): Promise<GlobbyFilterFunction>;
/**
@see isGitIgnored
@returns A filter function indicating whether a given path is ignored via a `.gitignore` file.
*/
export function isGitIgnoredSync(options?: GitignoreOptions): GlobbyFilterFunction;

View File

@@ -0,0 +1,229 @@
import fs from 'node:fs';
import nodePath from 'node:path';
import merge2 from 'merge2';
import fastGlob from 'fast-glob';
import dirGlob from 'dir-glob';
import {
GITIGNORE_FILES_PATTERN,
isIgnoredByIgnoreFiles,
isIgnoredByIgnoreFilesSync,
} from './ignore.js';
import {FilterStream, toPath, isNegativePattern} from './utilities.js';
const assertPatternsInput = patterns => {
if (patterns.some(pattern => typeof pattern !== 'string')) {
throw new TypeError('Patterns must be a string or an array of strings');
}
};
const toPatternsArray = patterns => {
patterns = [...new Set([patterns].flat())];
assertPatternsInput(patterns);
return patterns;
};
const checkCwdOption = options => {
if (!options.cwd) {
return;
}
let stat;
try {
stat = fs.statSync(options.cwd);
} catch {
return;
}
if (!stat.isDirectory()) {
throw new Error('The `cwd` option must be a path to a directory');
}
};
const normalizeOptions = (options = {}) => {
options = {
...options,
ignore: options.ignore || [],
expandDirectories: options.expandDirectories === undefined
? true
: options.expandDirectories,
cwd: toPath(options.cwd),
};
checkCwdOption(options);
return options;
};
const normalizeArguments = fn => async (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
const normalizeArgumentsSync = fn => (patterns, options) => fn(toPatternsArray(patterns), normalizeOptions(options));
const getIgnoreFilesPatterns = options => {
const {ignoreFiles, gitignore} = options;
const patterns = ignoreFiles ? toPatternsArray(ignoreFiles) : [];
if (gitignore) {
patterns.push(GITIGNORE_FILES_PATTERN);
}
return patterns;
};
const getFilter = async options => {
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
return createFilterFunction(
ignoreFilesPatterns.length > 0 && await isIgnoredByIgnoreFiles(ignoreFilesPatterns, options),
);
};
const getFilterSync = options => {
const ignoreFilesPatterns = getIgnoreFilesPatterns(options);
return createFilterFunction(
ignoreFilesPatterns.length > 0 && isIgnoredByIgnoreFilesSync(ignoreFilesPatterns, options),
);
};
const createFilterFunction = isIgnored => {
const seen = new Set();
return fastGlobResult => {
const path = fastGlobResult.path || fastGlobResult;
const pathKey = nodePath.normalize(path);
const seenOrIgnored = seen.has(pathKey) || (isIgnored && isIgnored(path));
seen.add(pathKey);
return !seenOrIgnored;
};
};
const unionFastGlobResults = (results, filter) => results.flat().filter(fastGlobResult => filter(fastGlobResult));
const unionFastGlobStreams = (streams, filter) => merge2(streams).pipe(new FilterStream(fastGlobResult => filter(fastGlobResult)));
const convertNegativePatterns = (patterns, options) => {
const tasks = [];
while (patterns.length > 0) {
const index = patterns.findIndex(pattern => isNegativePattern(pattern));
if (index === -1) {
tasks.push({patterns, options});
break;
}
const ignorePattern = patterns[index].slice(1);
for (const task of tasks) {
task.options.ignore.push(ignorePattern);
}
if (index !== 0) {
tasks.push({
patterns: patterns.slice(0, index),
options: {
...options,
ignore: [
...options.ignore,
ignorePattern,
],
},
});
}
patterns = patterns.slice(index + 1);
}
return tasks;
};
const getDirGlobOptions = (options, cwd) => ({
...(cwd ? {cwd} : {}),
...(Array.isArray(options) ? {files: options} : options),
});
const generateTasks = async (patterns, options) => {
const globTasks = convertNegativePatterns(patterns, options);
const {cwd, expandDirectories} = options;
if (!expandDirectories) {
return globTasks;
}
const patternExpandOptions = getDirGlobOptions(expandDirectories, cwd);
const ignoreExpandOptions = cwd ? {cwd} : undefined;
return Promise.all(
globTasks.map(async task => {
let {patterns, options} = task;
[
patterns,
options.ignore,
] = await Promise.all([
dirGlob(patterns, patternExpandOptions),
dirGlob(options.ignore, ignoreExpandOptions),
]);
return {patterns, options};
}),
);
};
const generateTasksSync = (patterns, options) => {
const globTasks = convertNegativePatterns(patterns, options);
const {cwd, expandDirectories} = options;
if (!expandDirectories) {
return globTasks;
}
const patternExpandOptions = getDirGlobOptions(expandDirectories, cwd);
const ignoreExpandOptions = cwd ? {cwd} : undefined;
return globTasks.map(task => {
let {patterns, options} = task;
patterns = dirGlob.sync(patterns, patternExpandOptions);
options.ignore = dirGlob.sync(options.ignore, ignoreExpandOptions);
return {patterns, options};
});
};
export const globby = normalizeArguments(async (patterns, options) => {
const [
tasks,
filter,
] = await Promise.all([
generateTasks(patterns, options),
getFilter(options),
]);
const results = await Promise.all(tasks.map(task => fastGlob(task.patterns, task.options)));
return unionFastGlobResults(results, filter);
});
export const globbySync = normalizeArgumentsSync((patterns, options) => {
const tasks = generateTasksSync(patterns, options);
const filter = getFilterSync(options);
const results = tasks.map(task => fastGlob.sync(task.patterns, task.options));
return unionFastGlobResults(results, filter);
});
export const globbyStream = normalizeArgumentsSync((patterns, options) => {
const tasks = generateTasksSync(patterns, options);
const filter = getFilterSync(options);
const streams = tasks.map(task => fastGlob.stream(task.patterns, task.options));
return unionFastGlobStreams(streams, filter);
});
export const isDynamicPattern = normalizeArgumentsSync(
(patterns, options) => patterns.some(pattern => fastGlob.isDynamicPattern(pattern, options)),
);
export const generateGlobTasks = normalizeArguments(generateTasks);
export const generateGlobTasksSync = normalizeArgumentsSync(generateTasksSync);
export {
isGitIgnored,
isGitIgnoredSync,
} from './ignore.js';

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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.

View File

@@ -0,0 +1,96 @@
{
"name": "globby",
"version": "13.2.2",
"description": "User-friendly glob matching",
"license": "MIT",
"repository": "sindresorhus/globby",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"email": "sindresorhus@gmail.com",
"name": "Sindre Sorhus",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"scripts": {
"bench": "npm update @globby/main-branch glob-stream fast-glob && node bench.js",
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts",
"ignore.js",
"utilities.js"
],
"keywords": [
"all",
"array",
"directories",
"expand",
"files",
"filesystem",
"filter",
"find",
"fnmatch",
"folders",
"fs",
"glob",
"globbing",
"globs",
"gulpfriendly",
"match",
"matcher",
"minimatch",
"multi",
"multiple",
"paths",
"pattern",
"patterns",
"traverse",
"util",
"utility",
"wildcard",
"wildcards",
"promise",
"gitignore",
"git"
],
"dependencies": {
"dir-glob": "^3.0.1",
"fast-glob": "^3.3.0",
"ignore": "^5.2.4",
"merge2": "^1.4.1",
"slash": "^4.0.0"
},
"devDependencies": {
"@globby/main-branch": "sindresorhus/globby#main",
"@types/node": "^20.3.3",
"ava": "^5.3.1",
"benchmark": "2.1.4",
"glob-stream": "^8.0.0",
"rimraf": "^5.0.1",
"tempy": "^3.0.0",
"tsd": "^0.28.1",
"typescript": "^5.1.6",
"xo": "^0.54.2"
},
"xo": {
"ignores": [
"fixtures"
],
"rules": {
"@typescript-eslint/consistent-type-definitions": "off",
"n/prefer-global/url": "off",
"@typescript-eslint/consistent-type-imports": "off"
}
},
"ava": {
"files": [
"!tests/utilities.js"
],
"workerThreads": false
}
}

View File

@@ -0,0 +1,183 @@
# globby
> User-friendly glob matching
Based on [`fast-glob`](https://github.com/mrmlnc/fast-glob) but adds a bunch of useful features.
## Features
- Promise API
- Multiple patterns
- Negated patterns: `['foo*', '!foobar']`
- Expands directories: `foo``foo/**/*`
- Supports `.gitignore` and similar ignore config files
- Supports `URL` as `cwd`
## Install
```
$ npm install globby
```
## Usage
```
├── unicorn
├── cake
└── rainbow
```
```js
import {globby} from 'globby';
const paths = await globby(['*', '!cake']);
console.log(paths);
//=> ['unicorn', 'rainbow']
```
## API
Note that glob patterns can only contain forward-slashes, not backward-slashes, so if you want to construct a glob pattern from path components, you need to use `path.posix.join()` instead of `path.join()`.
### globby(patterns, options?)
Returns a `Promise<string[]>` of matching paths.
#### patterns
Type: `string | string[]`
See supported `minimatch` [patterns](https://github.com/isaacs/minimatch#usage).
#### options
Type: `object`
See the [`fast-glob` options](https://github.com/mrmlnc/fast-glob#options-3) in addition to the ones below.
##### expandDirectories
Type: `boolean | string[] | object`\
Default: `true`
If set to `true`, `globby` will automatically glob directories for you. If you define an `Array` it will only glob files that matches the patterns inside the `Array`. You can also define an `object` with `files` and `extensions` like below:
```js
import {globby} from 'globby';
(async () => {
const paths = await globby('images', {
expandDirectories: {
files: ['cat', 'unicorn', '*.jpg'],
extensions: ['png']
}
});
console.log(paths);
//=> ['cat.png', 'unicorn.png', 'cow.jpg', 'rainbow.jpg']
})();
```
Note that if you set this option to `false`, you won't get back matched directories unless you set `onlyFiles: false`.
##### gitignore
Type: `boolean`\
Default: `false`
Respect ignore patterns in `.gitignore` files that apply to the globbed files.
##### ignoreFiles
Type: `string | string[]`\
Default: `undefined`
Glob patterns to look for ignore files, which are then used to ignore globbed files.
This is a more generic form of the `gitignore` option, allowing you to find ignore files with a [compatible syntax](http://git-scm.com/docs/gitignore). For instance, this works with Babel's `.babelignore`, Prettier's `.prettierignore`, or ESLint's `.eslintignore` files.
### globbySync(patterns, options?)
Returns `string[]` of matching paths.
### globbyStream(patterns, options?)
Returns a [`stream.Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) of matching paths.
Since Node.js 10, [readable streams are iterable](https://nodejs.org/api/stream.html#stream_readable_symbol_asynciterator), so you can loop over glob matches in a [`for await...of` loop](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) like this:
```js
import {globbyStream} from 'globby';
(async () => {
for await (const path of globbyStream('*.tmp')) {
console.log(path);
}
})();
```
### generateGlobTasks(patterns, options?)
Returns an `Promise<object[]>` in the format `{patterns: string[], options: Object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages.
Note that you should avoid running the same tasks multiple times as they contain a file system cache. Instead, run this method each time to ensure file system changes are taken into consideration.
### generateGlobTasksSync(patterns, options?)
Returns an `object[]` in the format `{patterns: string[], options: Object}`, which can be passed as arguments to [`fast-glob`](https://github.com/mrmlnc/fast-glob). This is useful for other globbing-related packages.
Takes the same arguments as `generateGlobTasks`.
### isDynamicPattern(patterns, options?)
Returns a `boolean` of whether there are any special glob characters in the `patterns`.
Note that the options affect the results.
This function is backed by [`fast-glob`](https://github.com/mrmlnc/fast-glob#isdynamicpatternpattern-options).
### isGitIgnored(options?)
Returns a `Promise<(path: URL | string) => boolean>` indicating whether a given path is ignored via a `.gitignore` file.
Takes `cwd?: URL | string` as options.
```js
import {isGitIgnored} from 'globby';
const isIgnored = await isGitIgnored();
console.log(isIgnored('some/file'));
```
### isGitIgnoredSync(options?)
Returns a `(path: URL | string) => boolean` indicating whether a given path is ignored via a `.gitignore` file.
Takes `cwd?: URL | string` as options.
## Globbing patterns
Just a quick overview.
- `*` matches any number of characters, but not `/`
- `?` matches a single character, but not `/`
- `**` matches any number of characters, including `/`, as long as it's the only thing in a path part
- `{}` allows for a comma-separated list of "or" expressions
- `!` at the beginning of a pattern will negate the match
[Various patterns and expected matches.](https://github.com/sindresorhus/multimatch/blob/main/test/test.js)
## globby for enterprise
Available as part of the Tidelift Subscription.
The maintainers of globby and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-globby?utm_source=npm-globby&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## Related
- [multimatch](https://github.com/sindresorhus/multimatch) - Match against a list instead of the filesystem
- [matcher](https://github.com/sindresorhus/matcher) - Simple wildcard matching
- [del](https://github.com/sindresorhus/del) - Delete files and directories
- [make-dir](https://github.com/sindresorhus/make-dir) - Make a directory and its parents if needed

View File

@@ -0,0 +1,17 @@
import {fileURLToPath} from 'node:url';
import {Transform} from 'node:stream';
export const toPath = urlOrPath => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
export class FilterStream extends Transform {
constructor(filter) {
super({
objectMode: true,
transform(data, encoding, callback) {
callback(undefined, filter(data) ? data : undefined);
},
});
}
}
export const isNegativePattern = pattern => pattern[0] === '!';

View File

@@ -0,0 +1,23 @@
/**
Convert Windows backslash paths to slash paths: `foo\\bar` ➔ `foo/bar`.
[Forward-slash paths can be used in Windows](http://superuser.com/a/176395/6877) as long as they're not extended-length paths and don't contain any non-ascii characters.
@param path - A Windows backslash path.
@returns A path with forward slashes.
@example
```
import path from 'path';
import slash from 'slash';
const string = path.join('foo', 'bar');
// Unix => foo/bar
// Windows => foo\\bar
slash(string);
// Unix => foo/bar
// Windows => foo/bar
```
*/
export default function slash(path: string): string;

View File

@@ -0,0 +1,10 @@
export default function slash(path) {
const isExtendedLengthPath = /^\\\\\?\\/.test(path);
const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex
if (isExtendedLengthPath || hasNonAscii) {
return path;
}
return path.replace(/\\/g, '/');
}

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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.

View File

@@ -0,0 +1,38 @@
{
"name": "slash",
"version": "4.0.0",
"description": "Convert Windows backslash paths to slash paths",
"license": "MIT",
"repository": "sindresorhus/slash",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=12"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"path",
"seperator",
"slash",
"backslash",
"windows",
"convert"
],
"devDependencies": {
"ava": "^3.15.0",
"tsd": "^0.14.0",
"xo": "^0.38.2"
}
}

View File

@@ -0,0 +1,48 @@
# slash
> Convert Windows backslash paths to slash paths: `foo\\bar` ➔ `foo/bar`
[Forward-slash paths can be used in Windows](http://superuser.com/a/176395/6877) as long as they're not extended-length paths and don't contain any non-ascii characters.
This was created since the `path` methods in Node.js outputs `\\` paths on Windows.
## Install
```
$ npm install slash
```
## Usage
```js
import path from 'path';
import slash from 'slash';
const string = path.join('foo', 'bar');
// Unix => foo/bar
// Windows => foo\\bar
slash(string);
// Unix => foo/bar
// Windows => foo/bar
```
## API
### slash(path)
Type: `string`
Accepts a Windows backslash path and returns a path with forward slashes.
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/npm-slash?utm_source=npm-slash&utm_medium=referral&utm_campaign=readme">Get professional support for this package with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>

View File

@@ -0,0 +1,15 @@
/**
Strip [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code) from a string.
@example
```
import stripAnsi from 'strip-ansi';
stripAnsi('\u001B[4mUnicorn\u001B[0m');
//=> 'Unicorn'
stripAnsi('\u001B]8;;https://github.com\u0007Click\u001B]8;;\u0007');
//=> 'Click'
```
*/
export default function stripAnsi(string: string): string;

View File

@@ -0,0 +1,9 @@
import ansiRegex from 'ansi-regex';
export default function stripAnsi(string) {
if (typeof string !== 'string') {
throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
}
return string.replace(ansiRegex(), '');
}

View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
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.

View File

@@ -0,0 +1,57 @@
{
"name": "strip-ansi",
"version": "7.0.1",
"description": "Strip ANSI escape codes from a string",
"license": "MIT",
"repository": "chalk/strip-ansi",
"funding": "https://github.com/chalk/strip-ansi?sponsor=1",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=12"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts"
],
"keywords": [
"strip",
"trim",
"remove",
"ansi",
"styles",
"color",
"colour",
"colors",
"terminal",
"console",
"string",
"tty",
"escape",
"formatting",
"rgb",
"256",
"shell",
"xterm",
"log",
"logging",
"command-line",
"text"
],
"dependencies": {
"ansi-regex": "^6.0.1"
},
"devDependencies": {
"ava": "^3.15.0",
"tsd": "^0.17.0",
"xo": "^0.44.0"
}
}

View File

@@ -0,0 +1,41 @@
# strip-ansi
> Strip [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code) from a string
## Install
```
$ npm install strip-ansi
```
## Usage
```js
import stripAnsi from 'strip-ansi';
stripAnsi('\u001B[4mUnicorn\u001B[0m');
//=> 'Unicorn'
stripAnsi('\u001B]8;;https://github.com\u0007Click\u001B]8;;\u0007');
//=> 'Click'
```
## strip-ansi for enterprise
Available as part of the Tidelift Subscription.
The maintainers of strip-ansi and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-strip-ansi?utm_source=npm-strip-ansi&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
## Related
- [strip-ansi-cli](https://github.com/chalk/strip-ansi-cli) - CLI for this module
- [strip-ansi-stream](https://github.com/chalk/strip-ansi-stream) - Streaming version of this module
- [has-ansi](https://github.com/chalk/has-ansi) - Check if a string has ANSI escape codes
- [ansi-regex](https://github.com/chalk/ansi-regex) - Regular expression for matching ANSI escape codes
- [chalk](https://github.com/chalk/chalk) - Terminal string styling done right
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [Josh Junon](https://github.com/qix-)

161
github/codeql-action-v2/node_modules/ava/package.json generated vendored Normal file
View File

@@ -0,0 +1,161 @@
{
"name": "ava",
"version": "5.3.1",
"description": "Node.js test runner that lets you develop with confidence.",
"license": "MIT",
"repository": "avajs/ava",
"homepage": "https://avajs.dev",
"bin": {
"ava": "entrypoints/cli.mjs"
},
"exports": {
".": {
"import": {
"types": "./entrypoints/main.d.ts",
"default": "./entrypoints/main.mjs"
},
"require": {
"types": "./entrypoints/main.d.cts",
"default": "./entrypoints/main.cjs"
}
},
"./eslint-plugin-helper": "./entrypoints/eslint-plugin-helper.cjs",
"./plugin": {
"import": {
"types": "./entrypoints/plugin.d.ts",
"default": "./entrypoints/plugin.mjs"
},
"require": {
"types": "./entrypoints/plugin.d.cts",
"default": "./entrypoints/plugin.cjs"
}
}
},
"type": "module",
"engines": {
"node": ">=14.19 <15 || >=16.15 <17 || >=18"
},
"scripts": {
"cover": "c8 --report=none test-ava && c8 --report=none --no-clean tap && c8 report",
"test": "xo && tsc --noEmit && npm run -s cover"
},
"files": [
"entrypoints",
"lib",
"types",
"*.d.ts"
],
"keywords": [
"🦄",
"test",
"runner",
"testing",
"ava",
"concurrent",
"parallel",
"fast",
"tdd",
"cli-app",
"cli",
"jest",
"mocha",
"tape",
"tap",
"qunit",
"jasmine",
"assert",
"assertion",
"promise",
"promises",
"async",
"function",
"await",
"generator",
"generators",
"yield",
"observable",
"observables",
"unit",
"snapshot",
"expect",
"typescript"
],
"dependencies": {
"acorn": "^8.8.2",
"acorn-walk": "^8.2.0",
"ansi-styles": "^6.2.1",
"arrgv": "^1.0.2",
"arrify": "^3.0.0",
"callsites": "^4.0.0",
"cbor": "^8.1.0",
"chalk": "^5.2.0",
"chokidar": "^3.5.3",
"chunkd": "^2.0.1",
"ci-info": "^3.8.0",
"ci-parallel-vars": "^1.0.1",
"clean-yaml-object": "^0.1.0",
"cli-truncate": "^3.1.0",
"code-excerpt": "^4.0.0",
"common-path-prefix": "^3.0.0",
"concordance": "^5.0.4",
"currently-unhandled": "^0.4.1",
"debug": "^4.3.4",
"emittery": "^1.0.1",
"figures": "^5.0.0",
"globby": "^13.1.4",
"ignore-by-default": "^2.1.0",
"indent-string": "^5.0.0",
"is-error": "^2.2.2",
"is-plain-object": "^5.0.0",
"is-promise": "^4.0.0",
"matcher": "^5.0.0",
"mem": "^9.0.2",
"ms": "^2.1.3",
"p-event": "^5.0.1",
"p-map": "^5.5.0",
"picomatch": "^2.3.1",
"pkg-conf": "^4.0.0",
"plur": "^5.1.0",
"pretty-ms": "^8.0.0",
"resolve-cwd": "^3.0.0",
"stack-utils": "^2.0.6",
"strip-ansi": "^7.0.1",
"supertap": "^3.0.1",
"temp-dir": "^3.0.0",
"write-file-atomic": "^5.0.1",
"yargs": "^17.7.2"
},
"devDependencies": {
"@ava/test": "github:avajs/test",
"@ava/typescript": "^4.0.0",
"@sindresorhus/tsconfig": "^3.0.1",
"ansi-escapes": "^6.2.0",
"c8": "^7.13.0",
"delay": "^5.0.0",
"execa": "^7.1.1",
"expect": "^29.5.0",
"fs-extra": "^11.1.1",
"get-stream": "^6.0.1",
"replace-string": "^4.0.0",
"sinon": "^15.1.0",
"tap": "^16.3.4",
"temp-write": "^5.0.0",
"tempy": "^3.0.0",
"touch": "^3.1.0",
"tsd": "^0.28.1",
"typescript": "^4.9.5",
"xo": "^0.54.2",
"zen-observable": "^0.10.0"
},
"peerDependencies": {
"@ava/typescript": "*"
},
"peerDependenciesMeta": {
"@ava/typescript": {
"optional": true
}
},
"volta": {
"node": "20.2.0"
}
}

3
github/codeql-action-v2/node_modules/ava/plugin.d.ts generated vendored Normal file
View File

@@ -0,0 +1,3 @@
// For compatibility with resolution algorithms other than Node16.
export * from './entrypoints/plugin.cjs';

231
github/codeql-action-v2/node_modules/ava/readme.md generated vendored Normal file
View File

@@ -0,0 +1,231 @@
[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://vshymanskyy.github.io/StandWithUkraine/)
# <img src="media/header.png" title="AVA" alt="AVA logo" width="530">
AVA is a test runner for Node.js with a concise API, detailed error output, embrace of new language features and process isolation that lets you develop with confidence 🚀
Follow the [AVA Twitter account](https://twitter.com/ava__js) for updates.
Read our [contributing guide](.github/CONTRIBUTING.md) if you're looking to contribute (issues / PRs / etc).
![](media/verbose-reporter.png)
Translations: [Español](https://github.com/avajs/ava-docs/blob/main/es_ES/readme.md), [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/readme.md), [Italiano](https://github.com/avajs/ava-docs/blob/main/it_IT/readme.md), [日本語](https://github.com/avajs/ava-docs/blob/main/ja_JP/readme.md), [한국어](https://github.com/avajs/ava-docs/blob/main/ko_KR/readme.md), [Português](https://github.com/avajs/ava-docs/blob/main/pt_BR/readme.md), [Русский](https://github.com/avajs/ava-docs/blob/main/ru_RU/readme.md), [简体中文](https://github.com/avajs/ava-docs/blob/main/zh_CN/readme.md)
## Why AVA?
- Minimal and fast
- Simple test syntax
- Runs tests concurrently
- Enforces writing atomic tests
- No implicit globals
- Includes TypeScript definitions
- [Magic assert](#magic-assert)
- [Isolated environment for each test file](./docs/01-writing-tests.md#process-isolation)
- [Promise support](./docs/01-writing-tests.md#promise-support)
- [Async function support](./docs/01-writing-tests.md#async-function-support)
- [Observable support](./docs/01-writing-tests.md#observable-support)
- [Enhanced assertion messages](./docs/03-assertions.md#enhanced-assertion-messages)
- [Automatic parallel test runs in CI](#parallel-runs-in-ci)
- [TAP reporter](./docs/05-command-line.md#tap-reporter)
## Usage
To install and set up AVA, run:
```console
npm init ava
```
Your `package.json` will then look like this (exact version notwithstanding):
```json
{
"name": "awesome-package",
"type": "module",
"scripts": {
"test": "ava"
},
"devDependencies": {
"ava": "^5.0.0"
}
}
```
Or if you prefer using Yarn:
```console
yarn add ava --dev
```
Alternatively you can install `ava` manually:
```console
npm install --save-dev ava
```
*Make sure to install AVA locally. As of AVA 4 it can no longer be run globally.*
Don't forget to configure the `test` script in your `package.json` as per above.
### Create your test file
Create a file named `test.js` in the project root directory.
_Note that AVA's documentation assumes you're using ES modules._
```js
import test from 'ava';
test('foo', t => {
t.pass();
});
test('bar', async t => {
const bar = Promise.resolve('bar');
t.is(await bar, 'bar');
});
```
### Running your tests
```console
npm test
```
Or with `npx`:
```console
npx ava
```
Run with the `--watch` flag to enable AVA's [watch mode](docs/recipes/watch-mode.md):
```console
npx ava --watch
```
## Supported Node.js versions
AVA supports the latest release of any major version that [is supported by Node.js itself](https://github.com/nodejs/Release#release-schedule). Read more in our [support statement](docs/support-statement.md).
## Highlights
### Magic assert
AVA adds code excerpts and clean diffs for actual and expected values. If values in the assertion are objects or arrays, only a diff is displayed, to remove the noise and focus on the problem. The diff is syntax-highlighted too! If you are comparing strings, both single and multi line, AVA displays a different kind of output, highlighting the added or missing characters.
![](media/magic-assert-combined.png)
### Clean stack traces
AVA automatically removes unrelated lines in stack traces, allowing you to find the source of an error much faster, as seen above.
### Parallel runs in CI
AVA automatically detects whether your CI environment supports parallel builds. Each build will run a subset of all test files, while still making sure all tests get executed. See the [`ci-parallel-vars`](https://www.npmjs.com/package/ci-parallel-vars) package for a list of supported CI environments.
## Documentation
Please see the [files in the `docs` directory](./docs):
* [Writing tests](./docs/01-writing-tests.md)
* [Execution context](./docs/02-execution-context.md)
* [Assertions](./docs/03-assertions.md)
* [Snapshot testing](./docs/04-snapshot-testing.md)
* [Command line (CLI)](./docs/05-command-line.md)
* [Configuration](./docs/06-configuration.md)
* [Test timeouts](./docs/07-test-timeouts.md)
### Common pitfalls
We have a growing list of [common pitfalls](docs/08-common-pitfalls.md) you may experience while using AVA. If you encounter any issues you think are common, comment in [this issue](https://github.com/avajs/ava/issues/404).
### Recipes
- [Test setup](docs/recipes/test-setup.md)
- [TypeScript](docs/recipes/typescript.md)
- [Shared workers](docs/recipes/shared-workers.md)
- [Watch mode](docs/recipes/watch-mode.md)
- [When to use `t.plan()`](docs/recipes/when-to-use-plan.md)
- [Passing arguments to your test files](docs/recipes/passing-arguments-to-your-test-files.md)
- [Splitting tests in CI](docs/recipes/splitting-tests-ci.md)
- [Code coverage](docs/recipes/code-coverage.md)
- [Endpoint testing](docs/recipes/endpoint-testing.md)
- [Browser testing](docs/recipes/browser-testing.md)
- [Testing Vue.js components](docs/recipes/vue.md)
- [Debugging tests with Chrome DevTools](docs/recipes/debugging-with-chrome-devtools.md)
- [Debugging tests with VSCode](docs/recipes/debugging-with-vscode.md)
- [Debugging tests with WebStorm](docs/recipes/debugging-with-webstorm.md)
- [Isolated MongoDB integration tests](docs/recipes/isolated-mongodb-integration-tests.md)
- [Testing web apps using Puppeteer](docs/recipes/puppeteer.md)
- [Testing web apps using Selenium WebDriverJS](docs/recipes/testing-with-selenium-webdriverjs.md)
## FAQ
### Why not `mocha`, `tape`, `tap`?
Mocha requires you to use implicit globals like `describe` and `it` with the default interface (which most people use). It's not very opinionated and executes tests serially without process isolation, making it slow.
Tape and tap are pretty good. AVA is highly inspired by their syntax. They too execute tests serially. Their default [TAP](https://testanything.org) output isn't very user-friendly though so you always end up using an external tap reporter.
In contrast AVA is highly opinionated and runs tests concurrently, with a separate process for each test file. Its default reporter is easy on the eyes and yet AVA still supports TAP output through a CLI flag.
### How is the name written and pronounced?
AVA, not Ava or ava. Pronounced [`/ˈeɪvə/`](media/pronunciation.m4a?raw=true): Ay (f**a**ce, m**a**de) V (**v**ie, ha**v**e) A (comm**a**, **a**go)
### What is the header background?
It's the [Andromeda galaxy](https://simple.wikipedia.org/wiki/Andromeda_galaxy).
### What is the difference between concurrency and parallelism?
[Concurrency is not parallelism. It enables parallelism.](https://stackoverflow.com/q/1050222)
## Support
- [GitHub Discussions](https://github.com/avajs/ava/discussions)
## Related
- [eslint-plugin-ava](https://github.com/avajs/eslint-plugin-ava) — Lint rules for AVA tests
- [@ava/typescript](https://github.com/avajs/typescript) — Test TypeScript projects
- [@ava/cooperate](https://github.com/avajs/cooperate) — Low-level primitives to enable cooperation between test files
- [@ava/get-port](https://github.com/avajs/get-port) — Reserve a port while testing
## Links
- [AVA stickers, t-shirts, etc](https://www.redbubble.com/people/sindresorhus/works/30330590-ava-logo)
- [Awesome list](https://github.com/avajs/awesome-ava)
- [Do you like AVA? Donate here!](https://opencollective.com/ava)
- [More…](https://github.com/avajs/awesome-ava)
## Team
[![Mark Wubben](https://github.com/novemberborn.png?size=100)](https://github.com/novemberborn) | [![Sindre Sorhus](https://github.com/sindresorhus.png?size=100)](https://github.com/sindresorhus)
---|---
[Mark Wubben](https://novemberborn.net) | [Sindre Sorhus](https://sindresorhus.com)
###### Former
- [Kevin Mårtensson](https://github.com/kevva)
- [James Talmage](https://github.com/jamestalmage)
- [Juan Soto](https://github.com/sotojuan)
- [Jeroen Engels](https://github.com/jfmengels)
- [Vadim Demedes](https://github.com/vadimdemedes)
<div align="center">
<br>
<br>
<br>
<a href="https://avajs.dev">
<img src="media/logo.svg" width="200" alt="AVA">
</a>
<br>
<br>
</div>

View File

@@ -0,0 +1,344 @@
export type ErrorConstructor<ErrorType extends Error = Error> = {
new (...args: any[]): ErrorType;
readonly prototype: ErrorType;
}
export type ThrownError<ErrorType extends ErrorConstructor | Error> = ErrorType extends ErrorConstructor ? ErrorType['prototype'] : ErrorType;
/** Specify one or more expectations the thrown error must satisfy. */
export type ThrowsExpectation<ErrorType extends ErrorConstructor | Error> = {
/** The thrown error must have a code that equals the given string or number. */
code?: string | number;
/** The thrown error must be an instance of this constructor. */
instanceOf?: ErrorType extends ErrorConstructor ? ErrorType : ErrorType extends Error ? ErrorConstructor<ErrorType> : never;
/** The thrown error must be strictly equal to this value. */
is?: ErrorType extends ErrorConstructor ? ErrorType['prototype'] : ErrorType;
/** The thrown error must have a message that equals the given string, or matches the regular expression. */
message?: string | RegExp | ((message: string) => boolean);
/** The thrown error must have a name that equals the given string. */
name?: string;
};
export type Assertions = {
/**
* Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), returning a boolean
* indicating whether the assertion passed.
*/
assert: AssertAssertion;
/**
* Assert that `actual` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to
* `expected`, returning a boolean indicating whether the assertion passed.
*/
deepEqual: DeepEqualAssertion;
/**
* Assert that `value` is like `selector`, returning a boolean indicating whether the assertion passed.
*/
like: LikeAssertion;
/** Fail the test, always returning `false`. */
fail: FailAssertion;
/**
* Assert that `actual` is strictly false, returning a boolean indicating whether the assertion passed.
*/
false: FalseAssertion;
/**
* Assert that `actual` is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), returning a boolean
* indicating whether the assertion passed.
*/
falsy: FalsyAssertion;
/**
* Assert that `actual` is [the same
* value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) as `expected`,
* returning a boolean indicating whether the assertion passed.
*/
is: IsAssertion;
/**
* Assert that `actual` is not [the same
* value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) as `expected`,
* returning a boolean indicating whether the assertion passed.
*/
not: NotAssertion;
/**
* Assert that `actual` is not [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to
* `expected`, returning a boolean indicating whether the assertion passed.
*/
notDeepEqual: NotDeepEqualAssertion;
/**
* Assert that `string` does not match the regular expression, returning a boolean indicating whether the assertion
* passed.
*/
notRegex: NotRegexAssertion;
/** Assert that the function does not throw. */
notThrows: NotThrowsAssertion;
/** Assert that the async function does not throw, or that the promise does not reject. Must be awaited. */
notThrowsAsync: NotThrowsAsyncAssertion;
/** Count a passing assertion, always returning `true`. */
pass: PassAssertion;
/**
* Assert that `string` matches the regular expression, returning a boolean indicating whether the assertion passed.
*/
regex: RegexAssertion;
/**
* Assert that `expected` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to a
* previously recorded [snapshot](https://github.com/concordancejs/concordance#serialization-details), or if
* necessary record a new snapshot.
*/
snapshot: SnapshotAssertion;
/**
* Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
*/
throws: ThrowsAssertion;
/**
* Assert that the async function throws [an error](https://www.npmjs.com/package/is-error), or the promise rejects
* with one. If so, returns a promise for the error value, which must be awaited.
*/
throwsAsync: ThrowsAsyncAssertion;
/**
* Assert that `actual` is strictly true, returning a boolean indicating whether the assertion passed.
*/
true: TrueAssertion;
/**
* Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), returning a boolean
* indicating whether the assertion passed.
*/
truthy: TruthyAssertion;
};
export type AssertAssertion = {
/**
* Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), returning a boolean
* indicating whether the assertion passed.
*/
(actual: any, message?: string): boolean;
/** Skip this assertion. */
skip(actual: any, message?: string): void;
};
export type DeepEqualAssertion = {
/**
* Assert that `actual` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to
* `expected`, returning a boolean indicating whether the assertion passed.
*/
<Actual, Expected extends Actual>(actual: Actual, expected: Expected, message?: string): actual is Expected;
/**
* Assert that `actual` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to
* `expected`, returning a boolean indicating whether the assertion passed.
*/
<Actual extends Expected, Expected>(actual: Actual, expected: Expected, message?: string): expected is Actual;
/**
* Assert that `actual` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to
* `expected`, returning a boolean indicating whether the assertion passed.
*/
<Actual, Expected>(actual: Actual, expected: Expected, message?: string): boolean;
/** Skip this assertion. */
skip(actual: any, expected: any, message?: string): void;
};
export type LikeAssertion = {
/**
* Assert that `value` is like `selector`, returning a boolean indicating whether the assertion passed.
*/
<Expected extends Record<string, any>>(value: any, selector: Expected, message?: string): value is Expected;
/** Skip this assertion. */
skip(value: any, selector: any, message?: string): void;
};
export type FailAssertion = {
/** Fail the test, always returning `false`. */
(message?: string): boolean;
/** Skip this assertion. */
skip(message?: string): void;
};
export type FalseAssertion = {
/**
* Assert that `actual` is strictly false, returning a boolean indicating whether the assertion passed.
*/
(actual: any, message?: string): actual is false;
/** Skip this assertion. */
skip(actual: any, message?: string): void;
};
export type FalsyAssertion = {
/**
* Assert that `actual` is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), returning a boolean
* indicating whether the assertion passed.
*/
(actual: any, message?: string): boolean;
/** Skip this assertion. */
skip(actual: any, message?: string): void;
};
export type IsAssertion = {
/**
* Assert that `actual` is [the same
* value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) as `expected`,
* returning a boolean indicating whether the assertion passed.
*/
<Actual, Expected extends Actual>(actual: Actual, expected: Expected, message?: string): actual is Expected;
/** Skip this assertion. */
skip(actual: any, expected: any, message?: string): void;
};
export type NotAssertion = {
/**
* Assert that `actual` is not [the same
* value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) as `expected`,
* returning a boolean indicating whether the assertion passed.
*/
<Actual, Expected>(actual: Actual, expected: Expected, message?: string): boolean;
/** Skip this assertion. */
skip(actual: any, expected: any, message?: string): void;
};
export type NotDeepEqualAssertion = {
/**
* Assert that `actual` is not [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to
* `expected`, returning a boolean indicating whether the assertion passed.
*/
<Actual, Expected>(actual: Actual, expected: Expected, message?: string): boolean;
/** Skip this assertion. */
skip(actual: any, expected: any, message?: string): void;
};
export type NotRegexAssertion = {
/**
* Assert that `string` does not match the regular expression, returning a boolean indicating whether the assertion
* passed.
*/
(string: string, regex: RegExp, message?: string): boolean;
/** Skip this assertion. */
skip(string: string, regex: RegExp, message?: string): void;
};
export type NotThrowsAssertion = {
/** Assert that the function does not throw. */
(fn: () => any, message?: string): void;
/** Skip this assertion. */
skip(fn: () => any, message?: string): void;
};
export type NotThrowsAsyncAssertion = {
/** Assert that the async function does not throw. You must await the result. */
(fn: () => PromiseLike<any>, message?: string): Promise<void>;
/** Assert that the promise does not reject. You must await the result. */
(promise: PromiseLike<any>, message?: string): Promise<void>;
/** Skip this assertion. */
skip(nonThrower: any, message?: string): void;
};
export type PassAssertion = {
/** Count a passing assertion, always returning `true`. */
(message?: string): boolean;
/** Skip this assertion. */
skip(message?: string): void;
};
export type RegexAssertion = {
/**
* Assert that `string` matches the regular expression, returning a boolean indicating whether the assertion passed.
*/
(string: string, regex: RegExp, message?: string): boolean;
/** Skip this assertion. */
skip(string: string, regex: RegExp, message?: string): void;
};
export type SnapshotAssertion = {
/**
* Assert that `expected` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to a
* previously recorded [snapshot](https://github.com/concordancejs/concordance#serialization-details), or if
* necessary record a new snapshot.
*/
(expected: any, message?: string): void;
/** Skip this assertion. */
skip(expected: any, message?: string): void;
};
export type ThrowsAssertion = {
/**
* Assert that the function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error value.
* The error must satisfy all expectations. Returns undefined when the assertion fails.
*/
<ErrorType extends ErrorConstructor | Error>(fn: () => any, expectations?: ThrowsExpectation<ErrorType>, message?: string): ThrownError<ErrorType> | undefined;
/** Skip this assertion. */
skip(fn: () => any, expectations?: any, message?: string): void;
};
export type ThrowsAsyncAssertion = {
/**
* Assert that the async function throws [an error](https://www.npmjs.com/package/is-error). If so, returns the error
* value. Returns undefined when the assertion fails. You must await the result. The error must satisfy all expectations.
*/
<ErrorType extends ErrorConstructor | Error>(fn: () => PromiseLike<any>, expectations?: ThrowsExpectation<ErrorType>, message?: string): Promise<ThrownError<ErrorType> | undefined>;
/**
* Assert that the promise rejects with [an error](https://www.npmjs.com/package/is-error). If so, returns the
* rejection reason. Returns undefined when the assertion fails. You must await the result. The error must satisfy all
* expectations.
*/
<ErrorType extends ErrorConstructor | Error>(promise: PromiseLike<any>, expectations?: ThrowsExpectation<ErrorType>, message?: string): Promise<ThrownError<ErrorType> | undefined>;
/** Skip this assertion. */
skip(thrower: any, expectations?: any, message?: string): void;
};
export type TrueAssertion = {
/**
* Assert that `actual` is strictly true, returning a boolean indicating whether the assertion passed.
*/
(actual: any, message?: string): actual is true;
/** Skip this assertion. */
skip(actual: any, message?: string): void;
};
export type TruthyAssertion = {
/**
* Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), returning a boolean
* indicating whether the assertion passed.
*/
(actual: any, message?: string): boolean;
/** Skip this assertion. */
skip(actual: any, message?: string): void;
};

Some files were not shown because too many files have changed in this diff Show More