ava 3.15.0 → 4.0.0

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 (72) hide show
  1. package/entrypoints/cli.mjs +4 -0
  2. package/entrypoints/eslint-plugin-helper.cjs +109 -0
  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 -816
  8. package/lib/api.js +108 -49
  9. package/lib/assert.js +255 -270
  10. package/lib/chalk.js +9 -14
  11. package/lib/cli.js +118 -112
  12. package/lib/code-excerpt.js +12 -17
  13. package/lib/concordance-options.js +29 -65
  14. package/lib/context-ref.js +3 -6
  15. package/lib/create-chain.js +32 -20
  16. package/lib/environment-variables.js +1 -4
  17. package/lib/eslint-plugin-helper-worker.js +73 -0
  18. package/lib/extensions.js +2 -2
  19. package/lib/fork.js +81 -84
  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 +11 -18
  26. package/lib/load-config.js +56 -180
  27. package/lib/module-types.js +3 -7
  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 -46
  34. package/lib/provider-manager.js +20 -14
  35. package/lib/reporters/beautify-stack.js +6 -12
  36. package/lib/reporters/colors.js +40 -15
  37. package/lib/reporters/default.js +114 -364
  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 +18 -25
  42. package/lib/run-status.js +29 -23
  43. package/lib/runner.js +157 -172
  44. package/lib/scheduler.js +53 -0
  45. package/lib/serialize-error.js +61 -64
  46. package/lib/snapshot-manager.js +271 -289
  47. package/lib/test.js +135 -291
  48. package/lib/watcher.js +69 -44
  49. package/lib/worker/base.js +208 -0
  50. package/lib/worker/channel.cjs +290 -0
  51. package/lib/worker/dependency-tracker.js +24 -23
  52. package/lib/worker/{ensure-forked.js → guard-environment.cjs} +5 -4
  53. package/lib/worker/line-numbers.js +58 -20
  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} +30 -21
  57. package/lib/worker/state.cjs +5 -0
  58. package/lib/worker/utils.cjs +6 -0
  59. package/package.json +71 -68
  60. package/plugin.d.ts +51 -53
  61. package/readme.md +5 -13
  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/eslint-plugin-helper.js +0 -201
  68. package/index.js +0 -8
  69. package/lib/worker/ipc.js +0 -201
  70. package/lib/worker/main.js +0 -21
  71. package/lib/worker/subprocess.js +0 -266
  72. package/plugin.js +0 -9
package/lib/chalk.js CHANGED
@@ -1,20 +1,15 @@
1
- 'use strict';
2
- const chalk = require('chalk');
1
+ import {Chalk} from 'chalk'; // eslint-disable-line unicorn/import-style
3
2
 
4
- let ctx = null;
5
- exports.get = () => {
6
- if (!ctx) {
7
- throw new Error('Chalk has not yet been configured');
8
- }
3
+ let chalk = new Chalk(); // eslint-disable-line import/no-mutable-exports
9
4
 
10
- return ctx;
11
- };
5
+ export {chalk};
12
6
 
13
- exports.set = options => {
14
- if (ctx) {
7
+ let configured = false;
8
+ export function set(options) {
9
+ if (configured) {
15
10
  throw new Error('Chalk has already been configured');
16
11
  }
17
12
 
18
- ctx = new chalk.Instance(options);
19
- return ctx;
20
- };
13
+ configured = true;
14
+ chalk = new Chalk(options);
15
+ }
package/lib/cli.js CHANGED
@@ -1,89 +1,112 @@
1
- 'use strict';
2
- const path = require('path');
3
- const del = require('del');
4
- const updateNotifier = require('update-notifier');
5
- const figures = require('figures');
6
- const arrify = require('arrify');
7
- const yargs = require('yargs');
8
- const readPkg = require('read-pkg');
9
- const isCi = require('./is-ci');
10
- const {loadConfig} = require('./load-config');
1
+
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import process from 'node:process';
5
+
6
+ import arrify from 'arrify';
7
+ import ciParallelVars from 'ci-parallel-vars';
8
+ import del from 'del';
9
+ import figures from 'figures';
10
+ import yargs from 'yargs';
11
+ import {hideBin} from 'yargs/helpers'; // eslint-disable-line node/file-extension-in-import
12
+
13
+ import Api from './api.js';
14
+ import {chalk} from './chalk.js';
15
+ import validateEnvironmentVariables from './environment-variables.js';
16
+ import normalizeExtensions from './extensions.js';
17
+ import {normalizeGlobs, normalizePattern} from './globs.js';
18
+ import {controlFlow} from './ipc-flow-control.cjs';
19
+ import isCi from './is-ci.js';
20
+ import {splitPatternAndLineNumbers} from './line-numbers.js';
21
+ import {loadConfig} from './load-config.js';
22
+ import normalizeModuleTypes from './module-types.js';
23
+ import normalizeNodeArguments from './node-arguments.js';
24
+ import providerManager from './provider-manager.js';
25
+ import DefaultReporter from './reporters/default.js';
26
+ import TapReporter from './reporters/tap.js';
27
+ import Watcher from './watcher.js';
11
28
 
12
29
  function exit(message) {
13
- console.error(`\n ${require('./chalk').get().red(figures.cross)} ${message}`);
30
+ console.error(`\n ${chalk.red(figures.cross)} ${message}`);
14
31
  process.exit(1); // eslint-disable-line unicorn/no-process-exit
15
32
  }
16
33
 
17
- const coerceLastValue = value => {
18
- return Array.isArray(value) ? value.pop() : value;
19
- };
34
+ const coerceLastValue = value => Array.isArray(value) ? value.pop() : value;
20
35
 
21
36
  const FLAGS = {
22
37
  concurrency: {
23
38
  alias: 'c',
24
39
  coerce: coerceLastValue,
25
40
  description: 'Max number of test files running at the same time (default: CPU cores)',
26
- type: 'number'
41
+ type: 'number',
27
42
  },
28
43
  'fail-fast': {
29
44
  coerce: coerceLastValue,
30
45
  description: 'Stop after first test failure',
31
- type: 'boolean'
46
+ type: 'boolean',
32
47
  },
33
48
  match: {
34
49
  alias: 'm',
35
50
  description: 'Only run tests with matching title (can be repeated)',
36
- type: 'string'
51
+ type: 'string',
52
+ },
53
+ 'no-worker-threads': {
54
+ coerce: coerceLastValue,
55
+ description: 'Don\'t use worker threads',
56
+ type: 'boolean',
37
57
  },
38
58
  'node-arguments': {
39
59
  coerce: coerceLastValue,
40
60
  description: 'Additional Node.js arguments for launching worker processes (specify as a single string)',
41
- type: 'string'
61
+ type: 'string',
42
62
  },
43
63
  serial: {
44
64
  alias: 's',
45
65
  coerce: coerceLastValue,
46
66
  description: 'Run tests serially',
47
- type: 'boolean'
67
+ type: 'boolean',
48
68
  },
49
69
  tap: {
50
70
  alias: 't',
51
71
  coerce: coerceLastValue,
52
72
  description: 'Generate TAP output',
53
- type: 'boolean'
73
+ type: 'boolean',
54
74
  },
55
75
  timeout: {
56
76
  alias: 'T',
57
77
  coerce: coerceLastValue,
58
78
  description: 'Set global timeout (milliseconds or human-readable, e.g. 10s, 2m)',
59
- type: 'string'
79
+ type: 'string',
60
80
  },
61
81
  'update-snapshots': {
62
82
  alias: 'u',
63
83
  coerce: coerceLastValue,
64
84
  description: 'Update snapshots',
65
- type: 'boolean'
85
+ type: 'boolean',
66
86
  },
67
87
  verbose: {
68
88
  alias: 'v',
69
89
  coerce: coerceLastValue,
70
- description: 'Enable verbose output',
71
- type: 'boolean'
90
+ description: 'Enable verbose output (default)',
91
+ type: 'boolean',
72
92
  },
73
93
  watch: {
74
94
  alias: 'w',
75
95
  coerce: coerceLastValue,
76
96
  description: 'Re-run tests when files change',
77
- type: 'boolean'
78
- }
97
+ type: 'boolean',
98
+ },
79
99
  };
80
100
 
81
- exports.run = async () => { // eslint-disable-line complexity
82
- let conf = {};
83
- let confError = null;
101
+ export default async function loadCli() { // eslint-disable-line complexity
102
+ let conf;
103
+ let confError;
84
104
  try {
85
- const {argv: {config: configFile}} = yargs.help(false);
105
+ const {argv: {config: configFile}} = yargs(hideBin(process.argv)).help(false);
86
106
  conf = await loadConfig({configFile});
107
+ if (conf.configFile && path.basename(conf.configFile) !== path.relative(conf.projectDir, conf.configFile)) {
108
+ console.log(chalk.magenta(` ${figures.warning} Using configuration from ${conf.configFile}`));
109
+ }
87
110
  } catch (error) {
88
111
  confError = error;
89
112
  }
@@ -91,18 +114,24 @@ exports.run = async () => { // eslint-disable-line complexity
91
114
  // Enter debug mode if the main process is being inspected. This assumes the
92
115
  // worker processes are automatically inspected, too. It is not necessary to
93
116
  // run AVA with the debug command, though it's allowed.
94
- const activeInspector = require('inspector').url() !== undefined; // eslint-disable-line node/no-unsupported-features/node-builtins
95
- let debug = activeInspector ?
96
- {
117
+ let activeInspector = false;
118
+ try {
119
+ const {default: inspector} = await import('node:inspector'); // eslint-disable-line node/no-unsupported-features/es-syntax
120
+
121
+ activeInspector = inspector.url() !== undefined;
122
+ } catch {}
123
+
124
+ let debug = activeInspector
125
+ ? {
97
126
  active: true,
98
127
  break: false,
99
128
  files: [],
100
129
  host: undefined,
101
- port: undefined
130
+ port: undefined,
102
131
  } : null;
103
132
 
104
133
  let resetCache = false;
105
- const {argv} = yargs
134
+ const {argv} = yargs(hideBin(process.argv))
106
135
  .parserConfiguration({
107
136
  'boolean-negation': true,
108
137
  'camel-case-expansion': false,
@@ -116,7 +145,7 @@ exports.run = async () => { // eslint-disable-line complexity
116
145
  'set-placeholder-key': false,
117
146
  'short-option-groups': true,
118
147
  'strip-aliased': true,
119
- 'unknown-options-as-args': false
148
+ 'unknown-options-as-args': false,
120
149
  })
121
150
  .usage('$0 [<pattern>...]')
122
151
  .usage('$0 debug [<pattern>...]')
@@ -124,16 +153,16 @@ exports.run = async () => { // eslint-disable-line complexity
124
153
  .options({
125
154
  color: {
126
155
  description: 'Force color output',
127
- type: 'boolean'
156
+ type: 'boolean',
128
157
  },
129
158
  config: {
130
- description: 'Specific JavaScript file for AVA to read its config from, instead of using package.json or ava.config.* files'
131
- }
159
+ description: 'Specific JavaScript file for AVA to read its config from, instead of using package.json or ava.config.* files',
160
+ },
132
161
  })
133
162
  .command('* [<pattern>...]', 'Run tests', yargs => yargs.options(FLAGS).positional('pattern', {
134
163
  array: true,
135
- describe: 'Glob patterns to select what test files to run. Leave empty if you want AVA to run all test files instead. Add a colon and specify line numbers of specific tests to run',
136
- type: 'string'
164
+ 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. Add a colon and specify line numbers of specific tests to run',
165
+ type: 'string',
137
166
  }), argv => {
138
167
  if (activeInspector) {
139
168
  debug.files = argv.pattern || [];
@@ -145,22 +174,22 @@ exports.run = async () => { // eslint-disable-line complexity
145
174
  yargs => yargs.options(FLAGS).options({
146
175
  break: {
147
176
  description: 'Break before the test file is loaded',
148
- type: 'boolean'
177
+ type: 'boolean',
149
178
  },
150
179
  host: {
151
180
  default: '127.0.0.1',
152
181
  description: 'Address or hostname through which you can connect to the inspector',
153
- type: 'string'
182
+ type: 'string',
154
183
  },
155
184
  port: {
156
185
  default: 9229,
157
186
  description: 'Port on which you can connect to the inspector',
158
- type: 'number'
159
- }
187
+ type: 'number',
188
+ },
160
189
  }).positional('pattern', {
161
190
  demand: true,
162
191
  describe: 'Glob patterns to select a single test file to debug. Add a colon and specify line numbers of specific tests to run',
163
- type: 'string'
192
+ type: 'string',
164
193
  }),
165
194
  argv => {
166
195
  debug = {
@@ -168,12 +197,12 @@ exports.run = async () => { // eslint-disable-line complexity
168
197
  break: argv.break === true,
169
198
  files: argv.pattern,
170
199
  host: argv.host,
171
- port: argv.port
200
+ port: argv.port,
172
201
  };
173
202
  })
174
203
  .command(
175
204
  'reset-cache',
176
- 'Reset AVA’s compilation cache and exit',
205
+ 'Delete any temporary files and state kept by AVA, then exit',
177
206
  yargs => yargs,
178
207
  () => {
179
208
  resetCache = true;
@@ -184,8 +213,14 @@ exports.run = async () => { // eslint-disable-line complexity
184
213
  .help();
185
214
 
186
215
  const combined = {...conf};
216
+
187
217
  for (const flag of Object.keys(FLAGS)) {
188
- if (Reflect.has(argv, flag)) {
218
+ if (flag === 'no-worker-threads' && Reflect.has(argv, 'worker-threads')) {
219
+ combined.workerThreads = argv['worker-threads'];
220
+ continue;
221
+ }
222
+
223
+ if (argv[flag] !== undefined) {
189
224
  if (flag === 'fail-fast') {
190
225
  combined.failFast = argv[flag];
191
226
  } else if (flag === 'update-snapshots') {
@@ -196,13 +231,15 @@ exports.run = async () => { // eslint-disable-line complexity
196
231
  }
197
232
  }
198
233
 
199
- const chalkOptions = {level: combined.color === false ? 0 : require('chalk').level};
200
- const chalk = require('./chalk').set(chalkOptions);
201
-
202
- if (combined.updateSnapshots && combined.match) {
203
- exit('Snapshots cannot be updated when matching specific tests.');
234
+ const chalkOptions = {level: 0};
235
+ if (combined.color !== false) {
236
+ const {supportsColor: {level}} = await import('chalk'); // eslint-disable-line node/no-unsupported-features/es-syntax, unicorn/import-style
237
+ chalkOptions.level = level;
204
238
  }
205
239
 
240
+ const {set: setChalk} = await import('./chalk.js'); // eslint-disable-line node/no-unsupported-features/es-syntax
241
+ setChalk(chalkOptions);
242
+
206
243
  if (confError) {
207
244
  if (confError.parent) {
208
245
  exit(`${confError.message}\n\n${chalk.gray((confError.parent && confError.parent.stack) || confError.parent)}`);
@@ -211,23 +248,23 @@ exports.run = async () => { // eslint-disable-line complexity
211
248
  }
212
249
  }
213
250
 
214
- updateNotifier({pkg: require('../package.json')}).notify();
215
-
216
251
  const {nonSemVerExperiments: experiments, projectDir} = conf;
217
252
  if (resetCache) {
218
253
  const cacheDir = path.join(projectDir, 'node_modules', '.cache', 'ava');
254
+
219
255
  try {
220
- await del('*', {
221
- cwd: cacheDir,
222
- nodir: true
223
- });
224
- console.error(`\n${chalk.green(figures.tick)} Removed AVA cache files in ${cacheDir}`);
256
+ const deletedFilePaths = await del('*', {cwd: cacheDir});
257
+
258
+ if (deletedFilePaths.length === 0) {
259
+ console.log(`\n${chalk.green(figures.tick)} No cache files to remove`);
260
+ } else {
261
+ console.log(`\n${chalk.green(figures.tick)} Removed AVA cache files in ${cacheDir}`);
262
+ }
263
+
225
264
  process.exit(0); // eslint-disable-line unicorn/no-process-exit
226
265
  } catch (error) {
227
266
  exit(`Error removing AVA cache files in ${cacheDir}\n\n${chalk.gray((error && error.stack) || error)}`);
228
267
  }
229
-
230
- return;
231
268
  }
232
269
 
233
270
  if (argv.watch) {
@@ -266,6 +303,10 @@ exports.run = async () => { // eslint-disable-line complexity
266
303
  console.log(chalk.magenta(` ${figures.warning} Experiments are enabled. These are unsupported and may change or be removed at any time.`));
267
304
  }
268
305
 
306
+ if (Reflect.has(conf, 'babel')) {
307
+ exit('Built-in Babel support has been removed.');
308
+ }
309
+
269
310
  if (Reflect.has(conf, 'compileEnhancements')) {
270
311
  exit('Enhancement compilation must be configured in AVA’s Babel options.');
271
312
  }
@@ -278,22 +319,9 @@ exports.run = async () => { // eslint-disable-line complexity
278
319
  exit('’sources’ has been removed. Use ’ignoredByWatcher’ to provide glob patterns of files that the watcher should ignore.');
279
320
  }
280
321
 
281
- const ciParallelVars = require('ci-parallel-vars');
282
- const Api = require('./api');
283
- const DefaultReporter = require('./reporters/default');
284
- const TapReporter = require('./reporters/tap');
285
- const Watcher = require('./watcher');
286
- const normalizeExtensions = require('./extensions');
287
- const normalizeModuleTypes = require('./module-types');
288
- const {normalizeGlobs, normalizePattern} = require('./globs');
289
- const normalizeNodeArguments = require('./node-arguments');
290
- const validateEnvironmentVariables = require('./environment-variables');
291
- const {splitPatternAndLineNumbers} = require('./line-numbers');
292
- const providerManager = require('./provider-manager');
293
-
294
322
  let pkg;
295
323
  try {
296
- pkg = readPkg.sync({cwd: projectDir});
324
+ pkg = JSON.parse(fs.readFileSync(path.resolve(projectDir, 'package.json')));
297
325
  } catch (error) {
298
326
  if (error.code !== 'ENOENT') {
299
327
  throw error;
@@ -303,26 +331,13 @@ exports.run = async () => { // eslint-disable-line complexity
303
331
  const {type: defaultModuleType = 'commonjs'} = pkg || {};
304
332
 
305
333
  const providers = [];
306
- if (Reflect.has(conf, 'babel')) {
307
- try {
308
- const {level, main} = providerManager.babel(projectDir);
309
- providers.push({
310
- level,
311
- main: main({config: conf.babel}),
312
- type: 'babel'
313
- });
314
- } catch (error) {
315
- exit(error.message);
316
- }
317
- }
318
-
319
334
  if (Reflect.has(conf, 'typescript')) {
320
335
  try {
321
- const {level, main} = providerManager.typescript(projectDir);
336
+ const {level, main} = await providerManager.typescript(projectDir);
322
337
  providers.push({
323
338
  level,
324
339
  main: main({config: conf.typescript}),
325
- type: 'typescript'
340
+ type: 'typescript',
326
341
  });
327
342
  } catch (error) {
328
343
  exit(error.message);
@@ -377,16 +392,14 @@ exports.run = async () => { // eslint-disable-line complexity
377
392
  .map(pattern => splitPatternAndLineNumbers(pattern))
378
393
  .map(({pattern, ...rest}) => ({
379
394
  pattern: normalizePattern(path.relative(projectDir, path.resolve(process.cwd(), pattern))),
380
- ...rest
395
+ ...rest,
381
396
  }));
382
- if (combined.updateSnapshots && filter.some(condition => condition.lineNumbers !== null)) {
383
- exit('Snapshots cannot be updated when selecting specific tests by their line number.');
384
- }
385
397
 
386
398
  const api = new Api({
387
399
  cacheEnabled: combined.cache !== false,
388
400
  chalkOptions,
389
401
  concurrency: combined.concurrency || 0,
402
+ workerThreads: combined.workerThreads !== false,
390
403
  debug,
391
404
  environmentVariables,
392
405
  experiments,
@@ -406,38 +419,31 @@ exports.run = async () => { // eslint-disable-line complexity
406
419
  snapshotDir: combined.snapshotDir ? path.resolve(projectDir, combined.snapshotDir) : null,
407
420
  timeout: combined.timeout || '10s',
408
421
  updateSnapshots: combined.updateSnapshots,
409
- workerArgv: argv['--']
422
+ workerArgv: argv['--'],
410
423
  });
411
424
 
412
425
  const reporter = combined.tap && !combined.watch && debug === null ? new TapReporter({
426
+ extensions: globs.extensions,
413
427
  projectDir,
414
428
  reportStream: process.stdout,
415
- stdStream: process.stderr
429
+ stdStream: process.stderr,
416
430
  }) : new DefaultReporter({
431
+ extensions: globs.extensions,
417
432
  projectDir,
418
433
  reportStream: process.stdout,
419
434
  stdStream: process.stderr,
420
435
  watching: combined.watch,
421
- verbose: debug !== null || combined.verbose || isCi || !process.stdout.isTTY
422
436
  });
423
437
 
424
438
  api.on('run', plan => {
425
439
  reporter.startRun(plan);
426
440
 
427
441
  if (process.env.AVA_EMIT_RUN_STATUS_OVER_IPC === 'I\'ll find a payphone baby / Take some time to talk to you') {
428
- const {controlFlow} = require('./ipc-flow-control');
429
442
  const bufferedSend = controlFlow(process);
430
443
 
431
- if (process.versions.node >= '12.16.0') {
432
- plan.status.on('stateChange', evt => {
433
- bufferedSend(evt);
434
- });
435
- } else {
436
- const v8 = require('v8');
437
- plan.status.on('stateChange', evt => {
438
- bufferedSend([...v8.serialize(evt)]);
439
- });
440
- }
444
+ plan.status.on('stateChange', evt => {
445
+ bufferedSend(evt);
446
+ });
441
447
  }
442
448
 
443
449
  plan.status.on('stateChange', evt => {
@@ -455,7 +461,7 @@ exports.run = async () => { // eslint-disable-line complexity
455
461
  globs,
456
462
  projectDir,
457
463
  providers,
458
- reporter
464
+ reporter,
459
465
  });
460
466
  watcher.observeStdin(process.stdin);
461
467
  } else {
@@ -476,4 +482,4 @@ exports.run = async () => { // eslint-disable-line complexity
476
482
  process.exitCode = runStatus.suggestExitCode({matching: match.length > 0});
477
483
  reporter.endRun();
478
484
  }
479
- };
485
+ }
@@ -1,14 +1,14 @@
1
- 'use strict';
2
- const fs = require('fs');
3
- const equalLength = require('equal-length');
4
- const codeExcerpt = require('code-excerpt');
5
- const truncate = require('cli-truncate');
6
- const chalk = require('./chalk').get();
1
+ import fs from 'node:fs';
2
+
3
+ import truncate from 'cli-truncate';
4
+ import codeExcerpt from 'code-excerpt';
5
+
6
+ import {chalk} from './chalk.js';
7
7
 
8
8
  const formatLineNumber = (lineNumber, maxLineNumber) =>
9
9
  ' '.repeat(Math.max(0, String(maxLineNumber).length - String(lineNumber).length)) + lineNumber;
10
10
 
11
- module.exports = (source, options = {}) => {
11
+ export default function exceptCode(source, options = {}) {
12
12
  if (!source.isWithinProject || source.isDependency) {
13
13
  return null;
14
14
  }
@@ -18,7 +18,7 @@ module.exports = (source, options = {}) => {
18
18
 
19
19
  let contents;
20
20
  try {
21
- contents = fs.readFileSync(file, 'utf8');
21
+ contents = fs.readFileSync(new URL(file), 'utf8');
22
22
  } catch {
23
23
  return null;
24
24
  }
@@ -30,25 +30,20 @@ module.exports = (source, options = {}) => {
30
30
 
31
31
  const lines = excerpt.map(item => ({
32
32
  line: item.line,
33
- value: truncate(item.value, maxWidth - String(line).length - 5)
33
+ value: truncate(item.value, maxWidth - String(line).length - 5),
34
34
  }));
35
35
 
36
- const joinedLines = lines.map(line => line.value).join('\n');
37
- const extendedLines = equalLength(joinedLines).split('\n');
36
+ const extendedWidth = Math.max(...lines.map(item => item.value.length));
38
37
 
39
38
  return lines
40
- .map((item, index) => ({
41
- line: item.line,
42
- value: extendedLines[index]
43
- }))
44
39
  .map(item => {
45
40
  const isErrorSource = item.line === line;
46
41
 
47
42
  const lineNumber = formatLineNumber(item.line, line) + ':';
48
43
  const coloredLineNumber = isErrorSource ? lineNumber : chalk.grey(lineNumber);
49
- const result = ` ${coloredLineNumber} ${item.value}`;
44
+ const result = ` ${coloredLineNumber} ${item.value.padEnd(extendedWidth)}`;
50
45
 
51
46
  return isErrorSource ? chalk.bgRed(result) : result;
52
47
  })
53
48
  .join('\n');
54
- };
49
+ }