Flow: Is there a way to tell Flow an exported module _is_ a class?

Created on 1 Sep 2015  路  10Comments  路  Source: facebook/flow

I'm trying to write a flow interface file for url-pattern.

This is how the module is used:

import UrlPattern from 'url-pattern';

var pattern = new UrlPattern('...');

Here's what I have in my interface file so far:

type UrlPatternOptions = { ... }

declare class UrlPattern {
  constructor(routePattern: string, options?: UrlPatternOptions): void;
  match(pathname: string): ?Object;
  stringify(params: Object): string;
}

declare module 'url-pattern' {
}

I can't seem to get that class to type check at all. I can do this to force it to recognize Pattern as the class UrlPattern, but then I get the error "Constructor cannot be called on UrlPattern".

import urlPattern from 'url-pattern';

var Pattern = (urlPattern: UrlPattern);
var pattern = new Pattern('...');
  1. Should that work or am I missing something?
  2. What do you think of a better syntax for declaring modules that export classes by default (mostly commonjs modules)? Something like this:
declare class UrlPattern {
  ...
}

declare module 'url-pattern': UrlPattern;

or this:

declare class UrlPattern {
  ...
}

declare module 'url-pattern' {
  *: UrlPattern;
};

Most helpful comment

It seems like the suggested approach here is a bit dangerous. The module's exported class is being declared in the global scope of your project. For example:

declarations.js:

declare class Thing {}

declare module 'thing' {
  declare var exports:typeof Thing;
}

source.js:

// Note intentional typo!
import Thng from 'thing';

const stuff = new Thing(); // This should be an error, but it isn't!

I think a better pattern is to scope the class, and maybe prefix it to be clear that it's not a real export:

declarations.js:

declare module 'thing' {
  declare class _Thing {}

  declare var exports:typeof _Thing;
}

All 10 comments

You are close:

declare class UrlPattern {
  ...
}

declare module 'url-pattern': typeof UrlPattern;

When you declare a class, it's the type of the instances of the class. You need to use typeof to annotate a class type itself.

Oh, interesting. I'll give that a shot. Did I miss this in the docs or is it new?

This is in the docs, but it can be a little confusing.

Closing, turns out I needed to ignore the node_modules folder even though it didn't contain flow types. Not making a separate issue as it's already being worked on. Thanks @nmn and @samwgoldman for the help here and in Reactiflux#flow!

Borrowing this issue even if it's closed, hope you don't mind.

I am trying to do this accoring to what @nmn suggested:

declare class [Class] {
  ...
}

declare module [Module]: typeof [Class];

where Class and Module is the respective module / class names.

However, I am getting the error Library parse errorwith Unexpected token :

I have tried every syntax I could think of, but can't get flow to accept it as a module declaration.

So, is this the recommended way to tell flow that a module is also a class?
I see usages of typeof in the docs, but not with declarations.

I am using the npm package flow-bin if that makes any difference.

Turns out exports is a special word in a module declaration. Try this:

declare class ...

declare module [module] {
  declare var exports: [class];
}

Works great!

Thanks a lot for the fast answer.

Just to update, after trying all these combinations, @spicydonuts's last comment was only partially right. Here is the code I want to type correctly:

var InlineStylePrefixer = require('inline-style-prefixer');

var getPrefixedStyle = function (
  style: Object,
  componentName: ?string,
  userAgent?: ?string,
): Object {
  var prefixer = new InlineStylePrefixer(userAgent);
  return prefixer.prefix(style);
};

module.exports = {
  getPrefixedStyle
};

And here is the definition file that worked:

declare class InlineStylePrefixer {
  constructor(userAgent?: ?string): void;

  prefix(style: Object): Object;
}

declare module 'inline-style-prefixer' {
  declare var exports: typeof InlineStylePrefixer;
}

Almost the same as the last comment, with the addition of typeof.

It seems like the suggested approach here is a bit dangerous. The module's exported class is being declared in the global scope of your project. For example:

declarations.js:

declare class Thing {}

declare module 'thing' {
  declare var exports:typeof Thing;
}

source.js:

// Note intentional typo!
import Thng from 'thing';

const stuff = new Thing(); // This should be an error, but it isn't!

I think a better pattern is to scope the class, and maybe prefix it to be clear that it's not a real export:

declarations.js:

declare module 'thing' {
  declare class _Thing {}

  declare var exports:typeof _Thing;
}

@nevir I agree, that's why you see hacky namespacing like this in flow-typed definitions:

declare class moment$Moment {
  ...
}

declare module "moment" {
  declare var exports: Class<moment$Moment>;
}
Was this page helpful?
0 / 5 - 0 ratings