avajs / ava

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

Ava `test()` expression is not callable / has no call signatures #2539

Closed dcsan closed 3 years ago

dcsan commented 4 years ago

I ran npm ava init in a project and created a first test as per ava docs.

Immediately the vscode editor is giving me a typescript warning error.

This expression is not callable.
  Type 'typeof import("/Users/dc/dev/exiteer/xbot/server/node_modules/ava/index")' has no call signatures.

image

I did look if there were separate typings but there is a index.d.ts as part of the basic module that's installed.

We'll also need your AVA configuration (in package.json or ava.config.* configuration files) and how you're invoking AVA. Share the installed AVA version (get it by running npx ava --version).

At this point i don't have any config files for ava, the initial install didn't seem to create.

FWIW this is a js file but I am leaving the typescript error checking running, i find it catches a lot of type errors even in plain JS files without type declarations.

novemberborn commented 4 years ago

My guess is that this is a mis-match between the type definition using export default and the import here using require(). @sindresorhus what do you think?

In https://github.com/avajs/ava/pull/2435 I'm switching to a CJS export, but that work's stalled. It'd be a breaking change I think.

dcsan commented 4 years ago

I think it might be cos I was trying to switch over from Jest. I'm not clear exactly but i think Jest does various things to your env... injecting globals somehow? This was for a project based on a boilerplate I used.

there's a jest.config.js sitting there, but not sure how that would take control of my system 🗡️

perhaps a part at the end of the

.eslintrc.js

module.exports = {
  parser: 'babel-eslint',

  parserOptions: {
    ecmaVersion: 2018,
  },
  extends: ['eslint:recommended', 'prettier'],
  env: {
    node: true,
  },
  rules: {
    'prettier/prettier': [
      'error',
      {
        trailingComma: 'es5',
        singleQuote: true,
        semi: false,
      },
    ],
    semi: [2, 'never'],
  },
  plugins: ['prettier', 'babel'],
  overrides: [
    {
      files: ['**/*.test.js'],
      env: {
        jest: true,
      },
    },
  ],
}
novemberborn commented 4 years ago

Oh right I thought this was some type output from VSCode but you're saying it's from ESLint?

sindresorhus commented 4 years ago

My guess is that this is a mis-match between the type definition using export default and the import here using require(). @sindresorhus what do you think?

Correct. See: https://github.com/sindresorhus/mem/issues/31

dcsan commented 4 years ago

Oh right I thought this was some type output from VSCode but you're saying it's from ESLint?

The message is within VS code, but i think from the typescript compile checker ts in the error code.

ts2349 shows up this issue in the TS repo https://github.com/microsoft/TypeScript/issues/35423

and const assertion as a solution

feljx commented 4 years ago

const test = require('ava').default instead of const test = require('ava') fixes it!

andria-dev commented 3 years ago

@feljx's solution does seem to work. This should be the documented way of using AVA or we should find a way to fix this type definition issue.

I have found a solution to this. If we replace the following line in the index.d.ts file, everything works as expected; there are no issues and the type definitions work properly with a regular require.

// Replace this
export default test;

// With this
export = test;

However, I'm unaware of how AVA's type definition file was made — i.e. automated or hand-made. If it was hand-made, I or someone else can make a PR for this, which should be simple given it is only a one-line change. If the generation of index.d.ts is automated, perhaps we can open up an issue with the tool that generated invalid type definitions.

novemberborn commented 3 years ago

AVA 4 (prereleases available) now ships with an ESM entrypoint that uses export default test, and a CJS entrypoint that module.exports = test. I'm hoping that the export default test type definition works for both, I'm really not sure whether you can have separate types for the different module systems.

andria-dev commented 3 years ago

@novemberborn export default test in a type definition .d.ts only works for ESM imports but export = test will work for both. Could we revisit the idea of using export = test? You can test this by creating the following type definition files and doing both import and require.

// test.js
module.exports = function (name) {
  return;
}
// test.d.ts
declare function test(name: string): void;
export default test; // this looks valid but does not work with VSCode for require.
// index.js
import test1 from './test';
const test2 = require('./test'); // The type is "import test2" instead of the function declaration above.

test1("a");
test2("b"); // This expression is not callable

Now if you replace export default test in the above example with export = test the types work as expected. The below video shows this in action.

https://user-images.githubusercontent.com/19195374/114308335-b65e2480-9aa0-11eb-8c9e-da374b376bc1.mp4

I also tested this with all plugins disabled in VSCode and it still happens. I also tried it with my TypeScript version set to typescript@latest.

andria-dev commented 3 years ago

And @novemberborn you can have different types for different module systems I believe. Each entry point needs its own .d.ts file anyway as far as I know. However, it would seem that it would be easier to use only export = test than maintain two different .d.ts files for the same source code.

novemberborn commented 3 years ago

OK, I'll have a play with your examples and AVA itself. It's not something I've run into myself. Do you know if it's somehow related to the TypeScript config?

you can have different types for different module systems I believe.

AVA 4 uses Node.js exports mapping, so it's transparent to the consumer. Use require() and you get the CJS entry point, use import and you get the ESM one. I don't know if TypeScript resolves that. Sounds complicated.

andria-dev commented 3 years ago

@novemberborn If you're referring to an individual project's TypeScript configuration, I don't think that is the issue because I didn't even have a TypeScript config for the above example. It could be related to how VSCode integrates with TypeScript for type checking related features perhaps, however, if that is the case, creating a fix in this repository would still benefit VSCode users while the upstream issue is being fixed.

novemberborn commented 3 years ago

If you're referring to an individual project's TypeScript configuration, I don't think that is the issue because I didn't even have a TypeScript config for the above example.

That's a great clue 😄

novemberborn commented 3 years ago

@andria-dev I can definitely reproduce this, but I don't think there's a solution.

export = test should be used with import test = require('ava') which isn't great. You can't export any other types either.

With export default test and const test = require('ava') VSCode assumes the imported test is an object with a default property. However AVA correctly exports both an ESM and a CJS module, so this isn't the case.

I think we need TypeScript support for this, https://github.com/microsoft/TypeScript/issues/33079 seems to be the relevant issue.

In the mean time, I'd rather stick to the ESM syntax.

simonhaenisch commented 3 years ago

How about also adding module.exports.test = test so that we can alternatively import it as a named import, i. e.

const { test } = require('ava');

?

novemberborn commented 3 years ago

https://github.com/avajs/ava/pull/2836 makes this work through require('ava').default.