avajs / ava

Node.js test runner that lets you develop with confidence 🚀
MIT License
20.74k stars 1.41k forks source link

Make line number selection work with @ava/babel & @ava/typescript #2473

Closed novemberborn closed 2 years ago

novemberborn commented 4 years ago

Line number selection relies on AVA parsing the test file. This does not work when you use a require hook (such as ts-node/register). That's OK. However we do want it to work with our Babel and TypeScript providers.

Ultimately, we need start and end lines & columns for each call expression in the test file. First we need to initialize the providers earlier:

https://github.com/avajs/ava/blob/1222ce9538e4890c20af695e558e3ee490f5a41b/lib/worker/subprocess.js#L45-L52

See here:

https://github.com/avajs/ava/blob/1222ce9538e4890c20af695e558e3ee490f5a41b/lib/worker/subprocess.js#L140-L159

Then, depending on the extension of the test file, we need to get the provider to give us the call locations. For normal JS files you can find that logic here:

https://github.com/avajs/ava/blob/1222ce9538e4890c20af695e558e3ee490f5a41b/lib/worker/line-numbers.js#L1-L28

For @ava/babel we need to parse the test file using Babel, with all the configured plugins active and whatnot.

For @ava/typescript, hopefully we can use typescript itself as a parser?

oantoro commented 4 years ago

Current implementation of @ava/babel manages source map through its worker() method. It installs source-map-support at line https://github.com/avajs/babel/blob/0949eaadfff2c7f8009f056c62c5f632ff7f0c6d/index.js#L359 I think it is good to move source map management functionality to AVA. So the providers can focus to:

  1. compile and cache source file
  2. load precompiled module
  3. parse source file and also add additional function canParse(file) to check if the provider can parse the given file

By moving source map management to AVA, we will get more flexibility to manage the stack trace. Maybe we could create new file source-map-manager.js which wraps source-map-support functionality. The implementation of source-map-support internally modifies the Error.prepareStackTrace https://github.com/evanw/node-source-map-support/blob/d29e9c81527346b6ec385f7ba27a7a1c487b69c7/source-map-support.js#L563 to generate original stack trace. In the future we could replace source-map-support with explicit implementation to solve https://github.com/avajs/ava/issues/2474 The source-map-manager.js is not coupled with test worker, so we also can use it in main process as long as the state of compiled source which hold source map location is provided https://github.com/avajs/ava/blob/78cfaa17fec615a1979a9aaa9855f6cc8eeeb4d8/lib/api.js#L194-L197

Example of source map manager usage:

const SourceMapManager = require('./source-map-manager');

const sourceMapManager = await new SourceMapManager({
  defaultStackTraceInference: 'compiled-stack'
});

sourceMapManager.installSourceMapHandler();

sourceMapManager.addState(state); // register `state` which is generated by provider, where `source-map-manager` could find the source map file

 // this can be used to replace `const sourceCallSite = sourceMapSupport.wrapCallSite(callSite);` in `worker/line-numbers.js`
// it will decide which source map to use and return original frame
sourceMapManager.getOriginalFrame(frame);

sourceMapManager.enableOriginalStackTrace(); // enable source map manager to infer original stack
const originalStack = (new Error('foo')).stack
sourceMapManager.disableOriginalStackTrace(); // leave source map manager to infer from compiled source again

// maybe also expose some lower level methods such as methods found in `SourceMapConsumer`

We can pass sourceMapManager to lineNumberSelection() along with providers so we can use to make line number selection work.

const getParser = providers => {
  for (const provider of providers) {
    if (provider.canParse(file)) {
      return provider.parse;
    }
  }
  // no provider can handle. use default
  return parse;
}

parse = getParser(providers);
const locations = parse(file);
// omitted lines
// ......
return () => {
  // omitted lines
  // .....
  const sourceCallSite = sourceMapManager.getOriginalFrame(callSite);
  const start = {
    line: sourceCallSite.getLineNumber(),
    column: sourceCallSite.getColumnNumber() - 1
  };

  const test = findTest(locations, start);
  // omitted lines
  // .....
}

In the future maybe we could add more providers such as coffee-script etc. Correct me if my interpretation is wrong.

novemberborn commented 4 years ago

I think it is good to move source map management functionality to AVA.

@okyantoro yes, see arno https://github.com/avajs/ava/issues/2474.

That said I'd prefer using a language specific AST to determine the line numbers.

In the future maybe we could add more providers such as coffee-script etc. Correct me if my interpretation is wrong.

I don't think it's wrong. AVA itself can focus on Node.js support, and other languages / dialects can be handled through providers. However we have limited resources and should refrain from supporting esoteric platforms.

alastairs commented 4 years ago

I hit this issue/#2474 yesterday with some tests I wrote in Typescript, and worked around it by using Babel to transpile my Typescript into Javascript for AVA to execute. It's... clunky. I see both issues are labelled help wanted - what help is needed at this point?

novemberborn commented 4 years ago

@alastairs I think the issue description is still accurate. Happy to help if you're getting stuck anywhere in particular.

novemberborn commented 2 years ago

AVA 4 removes @ava/babel. I think https://github.com/avajs/ava/pull/2859 may have fixed this issue for TypeScript.