Flow: Type annotating callable modules?

Created on 16 May 2015  路  12Comments  路  Source: facebook/flow

If you have a third-party CommonJS module like underscore, how do you annotate it? For example:

var _ = function (x) { return x + "!" }
_.add = function (x, y) { return x + y }

module.exports = _

I'm trying to write something like this (which is possible in TypeScript):

declare module "underscore" {
  declare function this (x: string) : string;
  declare function add (x:number, y:number) : number;
}

Is this possible in Flow?

Most helpful comment

@mindeavor, you can declare callable modules like so:

declare module "underscore" {
  declare var exports: {
    (x: string): string;
    add(x: number, y: number): number;
  }
}

And in flow code, it's no different:

type Test = {
  (): string;
  x: number
}

Let me know if that resolves your issue!

All 12 comments

You want the special export called exports. To export a function, something like this would do:

declare module "test" {
  declare function exports(): string;
}

Then to use:

import example from "test";

var string: number = example(); // error, number vs. string

@mindeavor Did the exports thing help you solve your problem, or are you still having an issue?

I'm having trouble getting flow to detect my declared module at all. I'm going to have to get back to you on that.

Maybe that is related to #447?

Wow, that was it, thank you. Ok, next problem :)

The declare module syntax seems different from normal annotation syntax. Is there a way to use intersection types within a declare module ? Here's what I'm trying to convert:

// This works great
type MProp<T>
  = () => T
  & (value: T) => T
  & { toJSON: () => T }
  ;

// This doesn't work, but shows what I want
declare module 'mithril' {
  redraw: (force?: boolean) => void
        & { strategy: MProp<string> };
}

// Existing use cases of the library I'm annotating
var m = require('mithril');
m.redraw()
m.redraw(true)
m.redraw.strategy(diff)

I need to do intersection types with exports too; I used the above example for better demonstration.

@mindeavor Take a look at the definitions in flow's on lib directory for some general guidance on the syntax. You need to have declare var redraw in your example.

Also, note that parentheses are important with union and intersection type definitions. This is called out in the docs, so make sure you understand everything there: http://flowtype.org/docs/union-intersection-types.html

Lastly, while I think the intersection between an object type and a function type is what you want, I'm not sure if that pattern actually works right now.

/* @flow */

type Test = (() => string) & { x: number }

function mkTest(): Test {
  var test = () => "test"
  return test // why does this work? we never defined `x`
}

So, even if you get the syntax right, I think there might be another bug, or maybe I don't understand intersection types properly.

For what it's worth, the following example _does_ throw a type error:

type Test = ((() => string) & { x: number })

var test : Test = function () {
  return 'hello'
}
// example.js:2:31,43: property x
// Property not found in
// example.js:4:19,6:1: function

And fixing it makes sense:

type Test = ((() => string) & { x: number })

var test : Test = (function () {
  var result = function () {
    return 'hello'
  }
  result.x = 10
  return result
})()

However, if you write this immediately afterwards, no type error is thrown :(

var wat : Object = test.iDontExist + 3
test('why')

@mindeavor, you can declare callable modules like so:

declare module "underscore" {
  declare var exports: {
    (x: string): string;
    add(x: number, y: number): number;
  }
}

And in flow code, it's no different:

type Test = {
  (): string;
  x: number
}

Let me know if that resolves your issue!

Looks great, thanks!

@samwgoldman how would I do this for a .js.flow file in my own package? declare function exports doesn't seem to work in that case.

This no longer works.

This slight modification of the above should still work:

declare module "underscore" {
  declare module.exports: {
    (x: string): string;
    add(x: number, y: number): number;
  }
}
Was this page helpful?
0 / 5 - 0 ratings