ava 4.0.0-alpha.1 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/entrypoints/cli.mjs +4 -0
  2. package/{eslint-plugin-helper.js → entrypoints/eslint-plugin-helper.cjs} +20 -7
  3. package/entrypoints/main.cjs +2 -0
  4. package/entrypoints/main.mjs +1 -0
  5. package/entrypoints/plugin.cjs +2 -0
  6. package/entrypoints/plugin.mjs +4 -0
  7. package/index.d.ts +6 -709
  8. package/lib/api.js +95 -46
  9. package/lib/assert.js +122 -173
  10. package/lib/chalk.js +9 -14
  11. package/lib/cli.js +105 -97
  12. package/lib/code-excerpt.js +12 -17
  13. package/lib/concordance-options.js +30 -31
  14. package/lib/context-ref.js +3 -6
  15. package/lib/create-chain.js +32 -4
  16. package/lib/environment-variables.js +1 -4
  17. package/lib/eslint-plugin-helper-worker.js +16 -26
  18. package/lib/extensions.js +2 -2
  19. package/lib/fork.js +42 -83
  20. package/lib/glob-helpers.cjs +140 -0
  21. package/lib/globs.js +136 -163
  22. package/lib/{ipc-flow-control.js → ipc-flow-control.cjs} +1 -0
  23. package/lib/is-ci.js +4 -2
  24. package/lib/like-selector.js +7 -13
  25. package/lib/line-numbers.js +10 -17
  26. package/lib/load-config.js +62 -56
  27. package/lib/module-types.js +3 -3
  28. package/lib/node-arguments.js +4 -5
  29. package/lib/{now-and-timers.js → now-and-timers.cjs} +0 -0
  30. package/lib/parse-test-args.js +22 -11
  31. package/lib/pkg.cjs +2 -0
  32. package/lib/plugin-support/shared-worker-loader.js +45 -48
  33. package/lib/plugin-support/shared-workers.js +24 -43
  34. package/lib/provider-manager.js +20 -14
  35. package/lib/reporters/beautify-stack.js +6 -11
  36. package/lib/reporters/colors.js +40 -15
  37. package/lib/reporters/default.js +115 -350
  38. package/lib/reporters/format-serialized-error.js +7 -18
  39. package/lib/reporters/improper-usage-messages.js +8 -9
  40. package/lib/reporters/prefix-title.js +17 -15
  41. package/lib/reporters/tap.js +15 -16
  42. package/lib/run-status.js +25 -23
  43. package/lib/runner.js +138 -127
  44. package/lib/scheduler.js +42 -36
  45. package/lib/serialize-error.js +34 -34
  46. package/lib/snapshot-manager.js +83 -76
  47. package/lib/test.js +114 -195
  48. package/lib/watcher.js +65 -40
  49. package/lib/worker/base.js +48 -99
  50. package/lib/worker/channel.cjs +290 -0
  51. package/lib/worker/dependency-tracker.js +22 -22
  52. package/lib/worker/guard-environment.cjs +19 -0
  53. package/lib/worker/line-numbers.js +57 -19
  54. package/lib/worker/main.cjs +12 -0
  55. package/lib/worker/{options.js → options.cjs} +0 -0
  56. package/lib/worker/{plugin.js → plugin.cjs} +31 -16
  57. package/lib/worker/state.cjs +5 -0
  58. package/lib/worker/{utils.js → utils.cjs} +1 -1
  59. package/package.json +60 -68
  60. package/plugin.d.ts +51 -53
  61. package/readme.md +5 -12
  62. package/types/assertions.d.ts +327 -0
  63. package/types/subscribable.ts +6 -0
  64. package/types/test-fn.d.ts +231 -0
  65. package/types/try-fn.d.ts +58 -0
  66. package/cli.js +0 -11
  67. package/index.js +0 -8
  68. package/lib/worker/channel.js +0 -218
  69. package/lib/worker/main.js +0 -20
  70. package/plugin.js +0 -9
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const chainRegistry = new WeakMap();
3
2
 
4
3
  function startChain(name, call, defaults) {
@@ -57,7 +56,7 @@ function createHookChain(hook, isAfterHook) {
57
56
  return hook;
58
57
  }
59
58
 
60
- function createChain(fn, defaults, meta) {
59
+ export default function createChain(fn, defaults, meta) {
61
60
  // Test chaining rules:
62
61
  // * `serial` must come at the start
63
62
  // * `only` and `skip` must come at the end
@@ -92,9 +91,38 @@ function createChain(fn, defaults, meta) {
92
91
  root.todo = startChain('test.todo', fn, {...defaults, type: 'test', todo: true});
93
92
  root.serial.todo = startChain('test.serial.todo', fn, {...defaults, serial: true, type: 'test', todo: true});
94
93
 
94
+ root.macro = options => {
95
+ if (typeof options === 'function') {
96
+ return Object.freeze({exec: options});
97
+ }
98
+
99
+ return Object.freeze({exec: options.exec, title: options.title});
100
+ };
101
+
95
102
  root.meta = meta;
96
103
 
104
+ // Our type definition uses ESM syntax; when using CJS with VSCode, the
105
+ // auto-completion assumes the root is accessed through `require('ava').default`.
106
+ // Placate VSCode by adding a mostly hidden default property on the root.
107
+ // This is available through both CJS and ESM imports. We use a proxy so that
108
+ // we don't end up with root.default.default.default chains.
109
+ Object.defineProperty(root, 'default', {
110
+ configurable: false,
111
+ enumerable: false,
112
+ writable: false,
113
+ value: new Proxy(root, {
114
+ apply(target, thisArg, argumentsList) {
115
+ target.apply(thisArg, argumentsList);
116
+ },
117
+ get(target, prop) {
118
+ if (prop === 'default') {
119
+ throw new TypeError('Cannot access default.default');
120
+ }
121
+
122
+ return target[prop];
123
+ },
124
+ }),
125
+ });
126
+
97
127
  return root;
98
128
  }
99
-
100
- module.exports = createChain;
@@ -1,5 +1,4 @@
1
- 'use strict';
2
- function validateEnvironmentVariables(environmentVariables) {
1
+ export default function validateEnvironmentVariables(environmentVariables) {
3
2
  if (!environmentVariables) {
4
3
  return {};
5
4
  }
@@ -12,5 +11,3 @@ function validateEnvironmentVariables(environmentVariables) {
12
11
 
13
12
  return environmentVariables;
14
13
  }
15
-
16
- module.exports = validateEnvironmentVariables;
@@ -1,33 +1,23 @@
1
- 'use strict';
2
- const v8 = require('v8');
3
- const {parentPort, workerData} = require('worker_threads');
1
+ import v8 from 'node:v8';
2
+ import {parentPort, workerData} from 'node:worker_threads';
4
3
 
5
- const {normalizeGlobs} = require('./globs');
6
- const normalizeExtensions = require('./extensions');
7
- const {loadConfig} = require('./load-config');
8
- const providerManager = require('./provider-manager');
4
+ import normalizeExtensions from './extensions.js';
5
+ import {normalizeGlobs} from './globs.js';
6
+ import {loadConfig} from './load-config.js';
7
+ import providerManager from './provider-manager.js';
9
8
 
10
9
  const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs.
11
10
 
12
11
  const configCache = new Map();
13
12
 
14
- const collectProviders = ({conf, projectDir}) => {
13
+ const collectProviders = async ({conf, projectDir}) => {
15
14
  const providers = [];
16
- if (Reflect.has(conf, 'babel')) {
17
- const {level, main} = providerManager.babel(projectDir);
18
- providers.push({
19
- level,
20
- main: main({config: conf.babel}),
21
- type: 'babel'
22
- });
23
- }
24
-
25
15
  if (Reflect.has(conf, 'typescript')) {
26
- const {level, main} = providerManager.typescript(projectDir);
16
+ const {level, main} = await providerManager.typescript(projectDir);
27
17
  providers.push({
28
18
  level,
29
19
  main: main({config: conf.typescript}),
30
- type: 'typescript'
20
+ type: 'typescript',
31
21
  });
32
22
  }
33
23
 
@@ -35,24 +25,24 @@ const collectProviders = ({conf, projectDir}) => {
35
25
  };
36
26
 
37
27
  const buildGlobs = ({conf, providers, projectDir, overrideExtensions, overrideFiles}) => {
38
- const extensions = overrideExtensions ?
39
- normalizeExtensions(overrideExtensions) :
40
- normalizeExtensions(conf.extensions, providers);
28
+ const extensions = overrideExtensions
29
+ ? normalizeExtensions(overrideExtensions)
30
+ : normalizeExtensions(conf.extensions, providers);
41
31
 
42
32
  return {
43
33
  cwd: projectDir,
44
34
  ...normalizeGlobs({
45
35
  extensions,
46
36
  files: overrideFiles ? overrideFiles : conf.files,
47
- providers
48
- })
37
+ providers,
38
+ }),
49
39
  };
50
40
  };
51
41
 
52
42
  const resolveGlobs = async (projectDir, overrideExtensions, overrideFiles) => {
53
43
  if (!configCache.has(projectDir)) {
54
- configCache.set(projectDir, loadConfig({resolveFrom: projectDir}).then(conf => { // eslint-disable-line promise/prefer-await-to-then
55
- const providers = collectProviders({conf, projectDir});
44
+ configCache.set(projectDir, loadConfig({resolveFrom: projectDir}).then(async conf => {
45
+ const providers = await collectProviders({conf, projectDir});
56
46
  return {conf, providers};
57
47
  }));
58
48
  }
package/lib/extensions.js CHANGED
@@ -1,4 +1,4 @@
1
- module.exports = (configuredExtensions, providers = []) => {
1
+ export default function resolveExtensions(configuredExtensions, providers = []) {
2
2
  // Combine all extensions possible for testing. Remove duplicate extensions.
3
3
  const duplicates = new Set();
4
4
  const seen = new Set();
@@ -43,4 +43,4 @@ module.exports = (configuredExtensions, providers = []) => {
43
43
  }
44
44
 
45
45
  return [...seen];
46
- };
46
+ }
package/lib/fork.js CHANGED
@@ -1,96 +1,58 @@
1
- 'use strict';
2
- const {Worker} = require('worker_threads');
3
- const childProcess = require('child_process');
4
- const path = require('path');
5
- const fs = require('fs');
6
- const Emittery = require('emittery');
7
- const {controlFlow} = require('./ipc-flow-control');
8
-
9
- if (fs.realpathSync(__filename) !== __filename) {
10
- console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.');
11
- }
12
-
13
- // In case the test file imports a different AVA install,
14
- // the presence of this variable allows it to require this one instead
15
- const AVA_PATH = path.resolve(__dirname, '..');
16
- const WORKER_PATH = require.resolve('./worker/base.js');
17
-
18
- class SharedWorkerChannel extends Emittery {
19
- constructor({channelId, filename, initialData}, sendToFork) {
20
- super();
21
-
22
- this.id = channelId;
23
- this.filename = filename;
24
- this.initialData = initialData;
25
- this.sendToFork = sendToFork;
26
- }
27
-
28
- signalReady() {
29
- this.sendToFork({
30
- type: 'shared-worker-ready',
31
- channelId: this.id
32
- });
33
- }
1
+ import childProcess from 'node:child_process';
2
+ import process from 'node:process';
3
+ import {fileURLToPath} from 'node:url';
4
+ import {Worker} from 'node:worker_threads';
34
5
 
35
- signalError() {
36
- this.sendToFork({
37
- type: 'shared-worker-error',
38
- channelId: this.id
39
- });
40
- }
6
+ import Emittery from 'emittery';
7
+ import {pEvent} from 'p-event';
41
8
 
42
- emitMessage({messageId, replyTo, serializedData}) {
43
- this.emit('message', {
44
- messageId,
45
- replyTo,
46
- serializedData
47
- });
48
- }
9
+ import {controlFlow} from './ipc-flow-control.cjs';
10
+ import serializeError from './serialize-error.js';
49
11
 
50
- forwardMessageToFork({messageId, replyTo, serializedData}) {
51
- this.sendToFork({
52
- type: 'shared-worker-message',
53
- channelId: this.id,
54
- messageId,
55
- replyTo,
56
- serializedData
57
- });
58
- }
12
+ let workerPath = new URL('worker/base.js', import.meta.url);
13
+ export function _testOnlyReplaceWorkerPath(replacement) {
14
+ workerPath = replacement;
59
15
  }
60
16
 
61
- let forkCounter = 0;
17
+ const additionalExecArgv = ['--enable-source-maps'];
62
18
 
63
19
  const createWorker = (options, execArgv) => {
64
20
  let worker;
65
21
  let postMessage;
66
22
  let close;
67
23
  if (options.workerThreads) {
68
- worker = new Worker(WORKER_PATH, {
24
+ worker = new Worker(workerPath, {
69
25
  argv: options.workerArgv,
70
- env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables, AVA_PATH},
71
- execArgv,
26
+ env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables},
27
+ execArgv: [...execArgv, ...additionalExecArgv],
72
28
  workerData: {
73
- options
29
+ options,
74
30
  },
75
31
  trackUnmanagedFds: true,
76
32
  stdin: true,
77
33
  stdout: true,
78
- stderr: true
34
+ stderr: true,
79
35
  });
80
36
  postMessage = worker.postMessage.bind(worker);
37
+
38
+ // Ensure we've seen this event before we terminate the worker thread, as a
39
+ // workaround for https://github.com/nodejs/node/issues/38418.
40
+ const starting = pEvent(worker, 'message', ({ava}) => ava && ava.type === 'starting');
41
+
81
42
  close = async () => {
82
43
  try {
44
+ await starting;
83
45
  await worker.terminate();
84
46
  } finally {
85
47
  // No-op
86
48
  }
87
49
  };
88
50
  } else {
89
- worker = childProcess.fork(WORKER_PATH, options.workerArgv, {
51
+ worker = childProcess.fork(fileURLToPath(workerPath), options.workerArgv, {
90
52
  cwd: options.projectDir,
91
53
  silent: true,
92
- env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables, AVA_PATH},
93
- execArgv
54
+ env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables},
55
+ execArgv: [...execArgv, ...additionalExecArgv],
94
56
  });
95
57
  postMessage = controlFlow(worker);
96
58
  close = async () => worker.kill();
@@ -99,15 +61,11 @@ const createWorker = (options, execArgv) => {
99
61
  return {
100
62
  worker,
101
63
  postMessage,
102
- close
64
+ close,
103
65
  };
104
66
  };
105
67
 
106
- module.exports = (file, options, execArgv = process.execArgv) => {
107
- // TODO: this can be changed to use `threadId` when using worker_threads
108
- const forkId = `fork/${++forkCounter}`;
109
- const sharedWorkerChannels = new Map();
110
-
68
+ export default function loadFork(file, options, execArgv = process.execArgv) {
111
69
  let finished = false;
112
70
 
113
71
  const emitter = new Emittery();
@@ -120,8 +78,7 @@ module.exports = (file, options, execArgv = process.execArgv) => {
120
78
  options = {
121
79
  baseDir: process.cwd(),
122
80
  file,
123
- forkId,
124
- ...options
81
+ ...options,
125
82
  };
126
83
 
127
84
  const {worker, postMessage, close} = createWorker(options, execArgv);
@@ -155,17 +112,19 @@ module.exports = (file, options, execArgv = process.execArgv) => {
155
112
  case 'ready-for-options':
156
113
  send({type: 'options', options});
157
114
  break;
158
-
159
115
  case 'shared-worker-connect': {
160
- const channel = new SharedWorkerChannel(message.ava, send);
161
- sharedWorkerChannels.set(channel.id, channel);
162
- emitter.emit('connectSharedWorker', channel);
116
+ const {channelId, filename, initialData, port} = message.ava;
117
+ emitter.emit('connectSharedWorker', {
118
+ filename,
119
+ initialData,
120
+ port,
121
+ signalError() {
122
+ send({type: 'shared-worker-error', channelId});
123
+ },
124
+ });
163
125
  break;
164
126
  }
165
127
 
166
- case 'shared-worker-message':
167
- sharedWorkerChannels.get(message.ava.channelId).emitMessage(message.ava);
168
- break;
169
128
  case 'ping':
170
129
  send({type: 'pong'});
171
130
  break;
@@ -175,7 +134,7 @@ module.exports = (file, options, execArgv = process.execArgv) => {
175
134
  });
176
135
 
177
136
  worker.on('error', error => {
178
- emitStateChange({type: 'worker-failed', err: error});
137
+ emitStateChange({type: 'worker-failed', err: serializeError('Worker error', false, error, file)});
179
138
  finish();
180
139
  });
181
140
 
@@ -196,7 +155,7 @@ module.exports = (file, options, execArgv = process.execArgv) => {
196
155
 
197
156
  return {
198
157
  file,
199
- forkId,
158
+ threadId: worker.threadId,
200
159
  promise,
201
160
 
202
161
  exit() {
@@ -214,6 +173,6 @@ module.exports = (file, options, execArgv = process.execArgv) => {
214
173
 
215
174
  onStateChange(listener) {
216
175
  return emitter.on('stateChange', listener);
217
- }
176
+ },
218
177
  };
219
- };
178
+ }
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+ const path = require('path');
3
+ const process = require('process');
4
+
5
+ const ignoreByDefault = require('ignore-by-default');
6
+ const picomatch = require('picomatch');
7
+ const slash = require('slash');
8
+
9
+ const defaultIgnorePatterns = [...ignoreByDefault.directories(), '**/node_modules'];
10
+ exports.defaultIgnorePatterns = defaultIgnorePatterns;
11
+
12
+ const defaultPicomatchIgnorePatterns = [
13
+ ...defaultIgnorePatterns,
14
+ // Unlike globby(), picomatch needs a complete pattern when ignoring directories.
15
+ ...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`),
16
+ ];
17
+
18
+ const defaultMatchNoIgnore = picomatch(defaultPicomatchIgnorePatterns);
19
+
20
+ const matchingCache = new WeakMap();
21
+ const processMatchingPatterns = input => {
22
+ let result = matchingCache.get(input);
23
+ if (!result) {
24
+ const ignore = [...defaultPicomatchIgnorePatterns];
25
+ const patterns = input.filter(pattern => {
26
+ if (pattern.startsWith('!')) {
27
+ // Unlike globby(), picomatch needs a complete pattern when ignoring directories.
28
+ ignore.push(pattern.slice(1), `${pattern.slice(1)}/**/*`);
29
+ return false;
30
+ }
31
+
32
+ return true;
33
+ });
34
+
35
+ result = {
36
+ match: picomatch(patterns, {ignore}),
37
+ matchNoIgnore: picomatch(patterns),
38
+ individualMatchers: patterns.map(pattern => ({pattern, match: picomatch(pattern, {ignore})})),
39
+ };
40
+ matchingCache.set(input, result);
41
+ }
42
+
43
+ return result;
44
+ };
45
+
46
+ exports.processMatchingPatterns = processMatchingPatterns;
47
+
48
+ const matchesIgnorePatterns = (file, patterns) => {
49
+ const {matchNoIgnore} = processMatchingPatterns(patterns);
50
+ return matchNoIgnore(file) || defaultMatchNoIgnore(file);
51
+ };
52
+
53
+ function classify(file, {cwd, extensions, filePatterns, ignoredByWatcherPatterns}) {
54
+ file = normalizeFileForMatching(cwd, file);
55
+ return {
56
+ isIgnoredByWatcher: matchesIgnorePatterns(file, ignoredByWatcherPatterns),
57
+ isTest: hasExtension(extensions, file) && !isHelperish(file) && filePatterns.length > 0 && matches(file, filePatterns),
58
+ };
59
+ }
60
+
61
+ exports.classify = classify;
62
+
63
+ const hasExtension = (extensions, file) => extensions.includes(path.extname(file).slice(1));
64
+
65
+ exports.hasExtension = hasExtension;
66
+
67
+ function isHelperish(file) { // Assume file has been normalized already.
68
+ // File names starting with an underscore are deemed "helpers".
69
+ if (path.basename(file).startsWith('_')) {
70
+ return true;
71
+ }
72
+
73
+ // This function assumes the file has been normalized. If it couldn't be,
74
+ // don't check if it's got a parent directory that starts with an underscore.
75
+ // Deem it not a "helper".
76
+ if (path.isAbsolute(file)) {
77
+ return false;
78
+ }
79
+
80
+ // If the file has a parent directory that starts with only a single
81
+ // underscore, it's deemed a "helper".
82
+ return path.dirname(file).split('/').some(dir => /^_(?:$|[^_])/.test(dir));
83
+ }
84
+
85
+ exports.isHelperish = isHelperish;
86
+
87
+ function matches(file, patterns) {
88
+ const {match} = processMatchingPatterns(patterns);
89
+ return match(file);
90
+ }
91
+
92
+ exports.matches = matches;
93
+
94
+ function normalizeFileForMatching(cwd, file) {
95
+ if (process.platform === 'win32') {
96
+ cwd = slash(cwd);
97
+ file = slash(file);
98
+ }
99
+
100
+ // Note that if `file` is outside `cwd` we can't normalize it. If this turns
101
+ // out to be a real-world scenario we may have to make changes in calling code
102
+ // to make sure the file isn't even selected for matching.
103
+ if (!file.startsWith(cwd)) {
104
+ return file;
105
+ }
106
+
107
+ // Assume `cwd` does *not* end in a slash.
108
+ return file.slice(cwd.length + 1);
109
+ }
110
+
111
+ exports.normalizeFileForMatching = normalizeFileForMatching;
112
+
113
+ function normalizePattern(pattern) {
114
+ // Always use `/` in patterns, harmonizing matching across platforms
115
+ if (process.platform === 'win32') {
116
+ pattern = slash(pattern);
117
+ }
118
+
119
+ if (pattern.endsWith('/')) {
120
+ pattern = pattern.slice(0, -1);
121
+ }
122
+
123
+ if (pattern.startsWith('./')) {
124
+ return pattern.slice(2);
125
+ }
126
+
127
+ if (pattern.startsWith('!./')) {
128
+ return `!${pattern.slice(3)}`;
129
+ }
130
+
131
+ return pattern;
132
+ }
133
+
134
+ exports.normalizePattern = normalizePattern;
135
+
136
+ function normalizePatterns(patterns) {
137
+ return patterns.map(pattern => normalizePattern(pattern));
138
+ }
139
+
140
+ exports.normalizePatterns = normalizePatterns;