Flow: way to extend/override flow/lib types

Created on 17 Apr 2015  ·  83Comments  ·  Source: facebook/flow

Built-in node.js type declarations are still far from being complete, and filling the missing parts is very cumbersome as Flow doesn't allow to overwrite single modules.

For instance, if I want to fill in some gaps in HTTP types, I have to use flow with --no-flowlib flag, which forces me to copy all the remaining built-in interfaces to my project.

Although such workaround works for now, it's unmaintainable in the longer term, and not obvious to newcomers.

Would it be a problem to make custom declarations override the default ones?

feature request

Most helpful comment

@popham why not to check types using your eyes?

+1 for topic

All 83 comments

Why not edit the lib files in place and rebuild Flow? You could pull request your modifications eventually.

@popham why not to check types using your eyes?

+1 for topic

I propose the following syntax for overriding flow types:

Firstly, we need a way to extend types that are not classes. The syntax is obviously Object spread:

type A = {a: boolean, b: number, c: string}
type B = {...A, d: (...rest: any)=> any}

Next, it should be possible to reassign a declaration:

declare class Document extends Document {
  newProp: boolean;
}
// OR
type Document = {...Document, newProp: boolean}

What's wrong with class MyDocument extends Document { newProp: boolean }? If your environment has a different Document than Flow's, then why did you use Flow's to begin with?

In these situations, my gut says that you should put the Flow libraries in your project somewhere under version control. When you need to override something, edit it. Any external libraries that you've incorporated into your project will be validated against your environment specification (the version controlled library files). (Corollary: If you're making a library, work against Flow's unaltered libraries.) I guess this could be facilitated by moving Flow's libraries to a separate repo so that users could shallow clone them into projects?

The counterargument goes: "I want my project to see updates to Flow's libraries." I say, "You want your environment specification to change at the whim of Flow's maintainers? You shouldn't." Sure Flow's libraries are still immature, but I think that the proper tact is to help improve them instead of implementing override support (this is low-hanging fruit for non-maintainers). Eventually they'll stabilize and track some ideal environment as it evolves with JS, popularity of Node, popularity of IE8, etc.

(I love the spread syntax, though. Although I would describe it as a non-disjoint union, not as an override.)

@nmn For your A/B example, you should be able to use intersection types: type B = A & { d: Function } (btw, Function is identical to your (...rest: any) => any type).

Whoops. Thanks @samwgoldman.

@samwgoldman Thanks for the clarification. I'm still wrapping my head around what intersections do. This makes a ton of sense. So I guess the only part is to be able to override types.

While I don't think there's anything wrong with

class MyDocument extends Document { newProp: boolean }

And I use it already. It would be nice to be able to add/modify properties on a type for specific environments.

By the way, I was wondering if the declare class syntax was pretty much redundant.
For example, the react type definitions have something that looks like this:

declare class ReactComponent<D, P, S> {
  getInitialState(): S;
  componentDidMount(): void;
  ...
}

type ReactClass = Class<ReactComponent>

Is that any different from defining it like so:

type ReactComponent<D, P, S> = {
  getInitialState(): S,
  componentDidMount(): void,
  ...
}

type ReactClass = Class<ReactComponent>

Also, as you mentioned above you can do extends by using intersection.

Just to clarify, I'm not hating on the class syntax (which can be a little confusing), but just trying to understand some of the subtleties of the type system.

Second part first. The difference is that you cannot extend or new the type ReactComponent version. Flow accepts that the declareed version exists in your JS environment (it's newable and extendable).

First part. The scope of a declare is global, so if you could override, you would be overriding for the entire project. I would argue that as an anti-feature.

  • If it's a class that you want to extend with additional or alternate methods, just extend it and add or override the relevant method.
  • If you disagree with a method's signature (so Flow won't let you override), then use --no-flowlib and provide your own declarations or you can go upstream to Flow's project repo and fix it for the benefit of everybody.

@popham If i'm not wrong, this is what I think: (Second part first)
You can't use the extend keyword but still get the same result:

while you would generally do:

declare class ReactSubComponent extends ReactComponent {
  someProp: any;
}

now you can do:

type ReactSubComponent = ReactComponent & {someProp: any}

As for being able to use the new keyword, instead of using typeof ReactComponent you can use Class<ReactComponent> (I'm not sure about this last part)


Now to the first part. At first I really liked the idea of getting rid of global type definitions.
Though this has some big advantages, this also has the huge downside of adding a ton of work to import types for every file. Project-wide type definitions actually make a lot of sense when you consider cross module inter-op. Having fewer type definitions is always better and will let flow do more type checking for you. So, I'm not sure it's going to be easy to get rid of the global declare method any time soon.

I'm having trouble understanding you. I've used Flow, I've used React, but I've never used them together, so I'll probably be a little sloppy. The following are coherent, independent narratives:

  • Suppose that I anticipate a bunch of React components that will have a someProp: string property. I'm going to explicate that shared structure by defining a type type ReactSubComponent = ReactComponent & {someProp: string}. Here's a couple of classes that satisfy my ReactSubComponent type:
class MyComponent extends ReactComponent {
  someProp: string;
  constructor() {
    super(...);
    this.someProp = "MyComponent";
  }
}

class YourComponent extends ReactComponent {
  someProp: string;
  constructor() {
    super(...);
    this.someProp = "YourComponent";
  }
}

Now I can create a function that rejects non-ReactComponents and rejects ReactComponents without a someProp: string property:

function printSubComponent(sc: ReactSubComponent): void {
  console.log(sc.someProp);
}

printSubComponent(new MyComponent());
printSubComponent(new YourComponent());
printSubComponent({someProp: "a string"}); // No good.  The object is not an instance of `ReactComponent`.
printSubComponent(new ReactComponent()); // No good.  This instance is missing the `someProp` property.

This subcomponent is just a type. If I try to create an instance, I'll get an error: var noGood1 = new ReactSubComponent();. If I try to extend this type, I'll get an error: class NoGood2 extends ReactSubComponent {...}.

  • Instead, suppose that I have a ReactSubComponent class that extends ReactComponent with a someProp: any property, but Flow doesn't have access to the class:
// Flow doesn't have access to this class.
// It is injected into the JS environment *somehow*.
class ReactSubComponent extends ReactComponent {
  constructor() {
    super(...);
    this.someProp = "ReactSubComponent"; // or `15` or `{randomProperty: ["three"]}`
  }
}

I'm going to attach this class to the global scope of my environment; the identifier ReactSubComponent provides my class from anywhere in my project (as long as it isn't masked). To instruct Flow of my class's existence, I've added a declaration:

declare class ReactSubComponent extends ReactComponent {
  someProp: any;
}

This declaration is _not_ type checked by Flow; it is inside a library file referenced from the .flowconfig file. Now from anywhere in my project (where the identifier isn't masked), I can get a ReactSubComponent:

var sc = new ReactSubComponent();
class MySubSubComponent extends ReactSubComponent {...}

This subcomponent is just a type. If I try to create an instance, I'll get an error: var noGood1 = new ReactSubComponent();. If I try to extend this type, I'll get an error: class NoGood2 extends ReactSubComponent {...}.

You're right about this. ReactSubComponent is just a type so you can't use it in your code at all! but you can use it to annotate classes you may define:

class someClass {...}
var x: ReactSubComponent = someClass

What is the preferred method for dealing with errors like this, when the built-in typedef is just incomplete?

 63:   http.globalAgent.maxSockets = settings.maxSockets;
            ^^^^^^^^^^^ property `globalAgent`. Property not found in
 63:   http.globalAgent.maxSockets = settings.maxSockets;
       ^^^^ module `http`

@STRML that's a problem with the type definitions library. You'll have to manually override the type definition for http.

Maybe something like:

var http: CustomHttpType = require('http');

Is there any way to do it globally, say in an interfaces file, so I don't
have to pollute the code?
On Dec 3, 2015 2:26 AM, "Naman Goel" [email protected] wrote:

@STRML https://github.com/STRML that's a problem with the type
definitions library. You'll have to manually override the type definition
for http.

Maybe something like:

var http: CustomHttpType = require('http');


Reply to this email directly or view it on GitHub
https://github.com/facebook/flow/issues/396#issuecomment-161549085.

In node it's very common to promisify the fs module with Bluebird, which adds methods like fs.openAsync(), fs.readdirAsync() etc. This pattern happens with a lot of other libraries too. It would be nice to have:

declare module fs extends fs {
  declare function openAsync(filename: string, mode: string): Promise<number>;
}

:+1: to this suggestion. It would really help when a definition file is just missing a few methods, rather than having to copy/paste the whole thing from the repo and edit.

:+1: here, as well.

My use-case is that I wish to Flow-typecheck my tests (or rather, to flip that on its' head, I wish to typecheck my _library_, using my tests.); but I always use Chai / should.js style assertions when not targeting _ancient_ engines like IE6.

Tests/giraphe.tests.es6.js:23
 23:          Walker.should.be.a('function')
                     ^^^^^^ property `should`. Property not found in
 28: export { Walker }
              ^^^^^^ statics of class expr `Walker`. See: giraphe.es6.js:28

Basically, while I understand some of the above arguments _against_ such things (hell, this back-and-forth goes back more than a decade, in some ways.), I'd say it boils down to … “Are you really telling me I simply can't use Flow, if my library extends another system's prototypes?” Whether that's extending Object.prototype, testing with should.js-style assertions, using npm modules that extend existing flow-typed libraries with new functionality, or simply dislocating behaviour of a conceptual ‘module’ in your own project across multiple files, using prototype-extension … this _happens_ in the JavaScript world. A lot.

I really, really think Flow should include a mechanism to support it, even if it's felt necessary to add warnings in the documentation that it's considered an anti-pattern, or exceptionally dangerous, or …

another reason to consider this is that one could generate flow type definitions from WebIDL (there's a lot of partial stuff in WebIDL)

Trying to add flow to my library that makes extensive use of chai's should object prototype extension. Is there really no way to extend Object.prototype when chai is imported (via a chai.js module declaration in [libs])?

As @ELLIOTTCABLE mentioned, prototype extension is fundamental and should not make the project incompatible.

These are the types of errors I get:

 41:   initialState.should.be.an('object')
                    ^^^^^^ property `should`. Property not found in
 41:   initialState.should.be.an('object')
       ^^^^^^^^^^^^ object type

@popham _(I'm new to Flow, so bear with me.)_ Seems to me that there's an obvious example of why this is a much needed feature - jQuery plugins. Type definition for jQuery is huge and looks like this:

declare class JQueryStatic {
  (selector: string, context?: Element | JQuery): JQuery;
  // ...
}

declare class JQuery {
  addClass(className: string): JQuery;
  // ...
}

declare var $: JQueryStatic;

When I use a jQuery plugin, e.g. Highcharts, it is added as a new method on the JQuery "class". Its use looks like this:

const chart = $('#myCoolChart', contextNode).highcharts({ ... });

There's no var to re-declare (as shown here), I need to extend the class contract. In this case if I copy the whole type definition into my project and edit it, there's no question of it being useful to anybody else in the future, as you suggested in other scenarios, because it's got nothing to do with jQuery itself. And I _do_ want to get updates of the base definition and it's _not_ an anti-pattern in this case - it's just a clean extension.

Yeap. Decided not to use flow because of issues like this:

element.style.msOverflowStyle = 'scrollbar';

// Error:(12, 15) Flow: property 'msOverflowStyle'. Property not found in CSSStyleDeclaration.

Whenever flow sees property it doesn't know, it throws an error.

Is there a way to work around this?

@le0nik

(element.style: any).msOverflowStyle = 'scrollbar';

@vkurchatkin thank you, it worked!

@vkurchatkin nice!

@vkurchatkin I've used the any workaround. Would you happen to know if a resolution is being worked on?

I'm running into quite the same problem with flowtype and chai plugins:

var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");

chai.use(chaiAsPromised);

var expect = chai.expect;

describe('Something', function() {
  it('returns a Promise', function() {
    return (expect(Promise.resolve()).to.eventually.resolve()
  })
})

Since the eventually property is injected by the chai-as-promised plugin, it is not known by flow.

Can this be somehow resolved?

I agree with @hon2a! For example i want add type definition of cordova's camera plugin. The problem is that the camera plugin attaches to the global navigator object the camera object but i can't add this property on Navigator class dynamically. I can only extend it.

I must be the only person using Flow that wants to add to something simple like string.

in TypeScript you can do
interface String {
toCamelCase(): string;
}
String.propotype.toCamelCase(): string {
// do the code
}
and you can do 'make_me_camel'.toCamelCase()

Can you do this in flow? I like flow better than TS because I can just use babel, jest, etc. and move on. But without something like this it is a non-starter since I don't want to find in all my code where I did something.toCamelCase() and change it to toCamelCase(something) - honestly this would not be too hard, but all the other methods I added to string make it harder.

I'm having the same issues as others (@siebertm, @cchamberlain, @ELLIOTTCABLE) in the thread around chai plugins. There's a flowtype libdef for base chai, but not for the various plugins (chai-as-promised, chai-enzyme, et al). I don't mind writing my own libdefs if I had some inkling of how to do this properly.

Right now, it simply looks like the libdef for chai includes additions for popular plugins, and I can go ahead an hack mine right in there. Is there a better way?

@idan I've since moved to a TypeScript project so not sure what the current landscape looks like, although skimming the thread it doesn't look like there is a way to extend prototypes well (aside from the any thing which isn't a real solution).

My use case is a union type.

I have a few built-in objects (with a "type" string property used for the union type) but I want to let applications using the library add their own object types to the union (_and benefit from Flow if they use it_).

Can the application using my library extend the union type?

It has to be an extension, they can't just use their own types, because the library doesn't know about the application's types, only its own, of course.

+1 on this.
I'm eval'ing Flow against TS currently.
I have found numerous places where I would have needed to extend some incomplete library .d.js file, and a couple where we extend a prototype, and I'm having to use $FlowFixMe for these.

Whereas in TS I can do:

// From coreUtilities.js
interface Array<T> {
    removeIf (cond: (x: T) => boolean): T[];
}

// Chrome specific implementation
interface Console {
    group(...x : any[]): void;
}

// Neither TS nor Flow libs seem to know about Function.name
// https://goo.gl/tJMykL
interface Function {
    name: string;
}

which works perfectly.

Its a real shame that flow doesn't support this like TS does, as it is better than TS in most other ways. The easy solution is as the previous poster says (and how TS does it) - to be able to declare interface multiple times for the same type, and merge them all together.

Its very useful and easy to be able to extend types from external libraries through prototype, but there is no way to tell flow about it.

@reednj It's not a shame. It's protection from implicit features from anywhere in favour of explicit class extending. Just define empty class with typed properties and assign it as a type. You don't even need to instantiate it.

class ExtendedElement extends Element {
  fullScreen: boolean
}
const element: ExtendedElement = (el: any);
element.fullScreen // works fine

Sure, it's a hack. But isn't it a hack to use browser specific features?

@TrySound: I understand that it makes it harder to reason about the classes, and that I can get around it with subclassing+casting, thats probably why C# and Java didn't have them at the start - they all added extension methods eventually though, because they are just so damn useful.

Edit: just to expand on my particular use case. I would like to be able to extend the native types, not add definitions for browser specific features.

For example it is much easier to do something like this n.sqrt().format(".02f"), than this NumberExtensions.format(Math.sqrt(n), ".02f")). Its no problem to create these methods by extending Number.prototype in js, but there is no way that I know of to make flow accept this (except by just casting everything to any). I want to be able to extend the type definitions to tell flow that these methods will exist at run time.

I understand this is dangerous, because it is possible for the definition and implementation to get out of sync without flow knowing about it. However I think the danger is worth it, which is probably why TypeScript supports it already.

@TrySound Please read my use case three posts above your response. It has nothing to do with classes and _needs_ the possibility to add to a union. I see no way around it, and the use case is legit and not even close to what one could call a "hack" IMO.

@reednj Extending built in stuff was always bad practice, so it will never be a use case for flow.
@lll000111 Pass types via generics. Extend unions with type Custom = Builtin | UserExtension.

@TrySound How does that help in my scenario? I use a disjoint union,. I tried the same scenario with generics before (spending months and a significant amount of effort in trying to get this to work since I too loved the concept) - there is a reason I went to disjoint unions. Whenever I plugged a hole soon a new one would open up, and the errors were very hard to track (suddenly 200 errors out of nowhere, all hard to track to any one single location). It was significantly harder and the code kept blowing up. Not because there is anything wrong with the code (yes you have to take my word, or don't) but because the scenario is just not well supported by these type systems. I will not go back to generics because it just does not work nearly as well. Extending the union types would be easy and pain-free. Trying to put the round thing into the rectangular generics hole is pain, I've tried.

There is only so much value in not discussing the actual problem but instead trying to tell people of other options without knowing their situation. Reminds of of StackOverflow... I'm not saying there is no value, thanks for the suggestions, but knowing my way around I still see the extension as the way to go.

There are plenty of issues with my overall scenario of providing a package with Flow-annotations in .js.flow files. Not infrequently I get errors in the calling package that I don't get when I run flow in the library package itself. For example, I finally included the test files in the flow configuration - they use the after-built files not the source files. Quite a few issues here too. Lots of hacks and workarounds all over the place. So we all struggle with the often shitty compromise of a 3rd party type system, and a _huge_ number of open tickets and slow progress because the makers of the tool have their use case covered (nothing wrong with that). And some of us have come to the conclusion that _our_ use cases would best be served by being able to extend types - which in addition seems to be a minimal coding effort. We _did_ think this through, this isn't an impulse wish.

Since giving complete and thorough background information about our respective projects and a full discussion would be a lot of trouble for _everyone_ (not to mention the effort of building code examples to show to the public - which in the end nobody is probably going to look at anyway, or how much time do even you want to spend on my specific scenario?) we have to have a little bit of trust in that what people say they need is what they need. So if we can just look at the number of upvotes here, this does not look like a fringe issue and I think we should keep the ticket on the original topic. If github would allow a side-tracking discussion to take place without the whole thread becoming unreadable I would certainly welcome this type of discussion, it's just that I think this place here is not well-suited. It is just not constructive.

I have objects which differ in one text property ("type") -- the ideal use case for disjoint unions, look at the documentation for that type of type. The only problem is that users of the library introduce new type strings. An extensible union type would solve the problem cleanly and cheaply (which includes considerations about effort and side-effects in Flow).

Well, you want hell of implicity.

Extending built in stuff was always bad practice, so it will never be a use case for flow.

@TrySound See my post above. Though opinionated, this might be understandable in case of extending natives. But when some libraries (e.g. jQuery) are based on extension, the argument simply doesn't hold.

...the argument simply doesn't hold.

Agreed, 100%. In a world where all the built-in and community libdefs were perfectly written, this argument would hold water. At the moment it's impossible to fix a single missing or mis-stated property or function without redefining a large chunk.

@STRML Just to add a concrete example: https://github.com/websockets/ws/issues/1173

Here we have THREE different types for a Websocket onerror callback function parameter:

  • Officially according to the specs the type can be Event or ErrorEvent (DOM types)
  • In the current Flow library definitions there only is Event
  • The most popular node.js package for websockets "ws" _actually_ sends an Error object, which is hard to change because it would be a breaking change and apparently nobody has complained yet.

And now? I'm stuck with not declaring a type in my callback function as a workaround, no way to reconcile those conflicts (especially with "ws" because the code would have to be changed). The real world is all about compromise and muddling through because perfection is neither achievable nor even necessary.

And that's just the example for actually broken code/lib.defs, not even a main argument made for this feature.

Just throwing this back into the discussion, as above, to prevent more responses like @TrySound's. This is literally a highlighted feature, on the landing page for Flow:

JAVASCRIPT, YOUR WAY

Flow is designed to understand idiomatic JavaScript. It understands common JavaScript patterns and many of the weird things we JavaScript developers love to do.

Flow isn't TypeScript, the reason it exists (or at least, one major reason) — at least as far as I can tell from the documentation and advertising copy — is to support existing JavaScript idioms.

And, you may not like it, but extending prototypes is what peak performance l— er, I mean, extending prototypes is a very common JavaScript idiom, utilized in a host of ways, across a host of (extremely popular) libraries!

We all know the advantages of avoiding doing this, the arguments against it, so on, and so forth. This isn't the place to discuss those.

Personal (depressing) opinion, the above being considered: until Flow handles this, Flow is completely and utterly useless. Use TypeScript instead. /=

Flow doesn't cover a lot of new es7 features that are released in current browsers and Node 8. Object.getOwnPropertyDescriptors being a great example.

Can't really do Object extends Object so you're stuck.

Another use is functions that change the prototype or properties of a React.Component.

One example that may be relevant is we use a component() decorator that actually merges together a variety of helpers (all in different modules). One example is a decorator that changes render() to accept render(props, state, context), another is one that adds minor helpers like this.setTimeout and this.setInterval, which ensure these things don't break on unmount. And so on. Some of them modify prototype functions, some add properties. These patterns are massively helpful for us. I suppose there may be a way to do many extends here, and maybe this is just something that I'm not clear on, but it seems unclear how do it in flow.

Basically, a big +1 to this issue, it's blocking almost all our valuable types from working.

Actually, to articulate the last paragraph a bit more clearly. The problem is we export these decorators from modules that should each export a type.

So an example is a SubscribableClass that adds a subscribable: CompositeDisposable property. There's a variety of these.

Now you want to later put them all together. I suppose Redux does things like this.

Seems like you'd force the end user to write something like:

declare class X extends SubType {}
declare class Y extends X extends OtherSubType {}

Or something? I'm actually not certain how you'd do it, again, I'm likely just in the dark here, but also seems like this area is not very flexible.

Yeah, I agree with all here requesting this feature, and especially @ELLIOTTCABLE with his quote main marketing phrase of the flow

Flow is designed to understand idiomatic JavaScript. It understands common JavaScript patterns and many of the weird things we JavaScript developers love to do.

I have added notifications for this issue and postoning using flow until this is resolved.

Flow is designed to understand idiomatic JavaScript. It understands common JavaScript patterns and many of the weird things we JavaScript developers love to do.

Speaking of common idiomatic JavaScript patterns that Flow doesn't support (any longer): When creating classes which extend another class you can no longer override the type signature of methods or static properties of classes you're extending:

declare class Foo extends Object { // error
  static bind(): Foo;              // error
  valueOf(): "flow, you're drunk"; // error
};

class Bar extends Object {         // error
  static bind() { return new Bar } // error
  valueOf() { return 'go home' }   // error
}

Try Flow

Side note, it's weird that the issue has the label Closed: wontfix yet it hasn't actually been closed.

5018 Still not possible to extend using &...

Has the Flow team addressed this issue anywhere?

We need a solution for extending an existing type, while keeping the property list immutable by default.

declare interface Point {
  x: number;
  y: number;
}

const point: Point = {x: 0, y: 1}
point.z = 2 // Error!

Here's my proposal: #5364

It's also a bit awkward currently to adopt post-ES6 features - now that the spec is being updated more frequently there's perfectly reasonable stuff that either is standard or soon will be that's currently difficult to use.

For example: Promise.finally, currently stage 3.

I know that's not strictly official yet, but it will be soon. I'm not trying to extend a builtin; I'm just trying to keep up with an evolving language spec.

The babel team has done an excellent job with presets to make this type of stuff pluggable. It would be nice if flow allowed developers to keep up in a similar way.

Another vote for overwriting types:

For example, in mongoose when you create a database schema you define a number of fields.
e.g. flow-typed/.../mongoose_v4.x.x/test_mongoose-v4.js#L32

but if you want to overwrite the _id field and not use this type:

_id: bson$ObjectId | string | number;

with something like this...

export class HashtagDoc /*:: extends Mongoose$Document */ {
  _id: string;
}

you can't:

Error: server/models/hashtag.model.js:16
 16:   _id: string;
            ^^^^^^ string. This type is incompatible with
270:   _id: bson$ObjectId | string | number;
            ^^^^^^^^^^^^^ bson$ObjectId. See lib: flow-typed/npm/mongoose_v4.x.x.js:270

Error: server/models/hashtag.model.js:16
 16:   _id: string;
            ^^^^^^ string. This type is incompatible with
270:   _id: bson$ObjectId | string | number;
                                     ^^^^^^ number. See lib: flow-typed/npm/mongoose_v4.x.x.js:270

Maybe a syntax like this would be nice?!:

export class HashtagDoc /*:: extends Mongoose$Document */ {
  _id: string!; // see the explamation mark ( ! )
}

@gianpaj I wouldn't call that "overriding" at all.
Your new type is a strict subtype of the original.
It should just work, and without any special syntax. It does precisely this in TypeScript.

Over 2 years down the line and still isn't addressed? 👎

Presently have a great use case for this, as someone who used typescript more frequently in the past I was surprised to discover this ability absent from flow (declaration merging)

Yeah so we recently had to type a "plugin" for a package on NPM and because flow lacked this support ... we copy and pasted the entire types of the package into a separate flow-typed folder and had to modify it by hand :( took about 1 man day to troubleshoot and work around.

if only i knew ocaml 😞

@calebmer @vkurchatkin could you please explain why this issue is marked as won't fixed?

I have a new use-case to discuss.

Reasoning

We try to write types for Vue. And we are not able to. See this pull request to vuejs/vue.
Vue uses a method called .use() to install new plugins. That's how it works:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
// now you can use `this.$store` inside components

We need to extend types here. Because there are plugins that provide lots of new fields: $http, $axios, $route, and many others. Since, there is no way to do it now. Which resolves in errors like:

error Cannot get app.$axios because property $axios is missing in Vue (see line 26)

Which is not true. That makes flow nearly unusable for this use-case. And that's a big project.

Possible solutions

There were many proposed solutions to this issue. I am not good enough with flow to suggest any of them to the core team.

Hidden issues/complexity

Are there any hidden issue why this issue can not be resolved? Maybe there are some philosophical reasons behind it?

My contribution

I can donate some money to the folks who would implement this. Let's say 100$. That's not much, but I am sure others will support me too.

Just here to repeat my dissapointment with lack of interest for this by flow team.

could you please explain why this issue is marked as won't fixed?

@sobolevn not sure, guessing it stems from the discussion at #4845

Are there any hidden issue why this issue can not be resolved?

After a 5 min look, none seem unresolvable, though it could potentially be a big code change.

Maybe there are some philosophical reasons behind it?

Idk, though it seems like it's just not a priority since there's technically a solution: you can use your own definitions instead of the ones provided. In theory you'd just need to copy flow/lib to your project and work from there, extending as you see fit. Has anyone checked if there's a higher quality definitions by third parties anywhere?

@hrgdavor I'm sorry to hear you're disappointed-- perhaps a good way to channel that frustration would be starting a good 3rd party libdef (:

@mrkev thanks for the detailed answer!

Idk, though it seems like it's just not a priority since there's technically a solution: you can use your own definitions instead of the ones provided. In theory you'd just need to copy flow/lib to your project and work from there, extending as you see fit.

It sound like a nightmare to me. I would personally prefer to have no types at all than to use some local, modified, outdated information.

After a 5 min look, none seem unresolvable, though it could potentially be a big code change.

Sounds promising. I really hope that this will be achieved. And we will have types for Vue.
I am facing some much problems with this issue in my experiments: https://github.com/sobolevn/vue-flow-typed

Is there anything I can help with?

For things like React, it'd be great to be able to extend the core types temporarily.

For example, react-native is currently at 0.55.4, which has Flow v0.67.1. It does, however, support React 16.3. I'd like to use createRef, which exists in Flow 0.72, but it's not available in 0.67. It'd be great to be able to extend the react module with a single definition for createRef instead of having to copy across the entire react definition from here.

Not sure it's intentional, but this workaround seems to be working for me. Say, I have a case for process.myServiceId = process.env.MY_SERVICE_ID;. I added flow-typed/node.js with this:

class MyProcess extends Process {
    myServiceId: ?string;
}

declare var process: MyProcess;

Yeah, that works well for certain globals. But if you want to e.g. change the type of a field on EventTarget there's no good way that I know of to update it such that it will actually apply in all the many ways you might receive or pass an EventTarget.

I am not able to use chai and chai-spies with Flow for that very reason. Just no way to tell that chai.use(spies) has side effects and which ones.

For anyone coming into this looking for a solution, @nmn's post (https://github.com/facebook/flow/issues/396#issuecomment-125542040) earlier is now possible.

_For example_,

declare interface Document extends Document {
  mozHidden: boolean,
  msHidden: boolean,
  webkitHidden: boolean,
}

That still does not work with custom types.

declare interface Some {
  a: string
}

declare interface Some extends Some {
  b: string
}
5: declare interface Some extends Some {
                     ^ Cannot declare `Some` [1] because the name is already bound.
References:
1: declare interface Some {
                     ^ [1]

https://flow.org/try/#0CYUwxgNghgTiAEBLAdgFxDAZlMCDKA9gLYIDeAUPPFAFzwDOqMKA5uQL7nmiSwIrosOfMQQgAHumTB68QiXgUqAIzqNmyNpyA

Any idea on when (if ever) flow will support extending existing modules? @deecewan makes a good point and now we are facing the same issue with forwardRef for react ...

I came up with the following solution to my Vue problem:

// @flow

import Vue from 'vue'

import type { Axios } from 'axios'
import type { Store } from 'vuex'
import type Router from 'vue-router'

import type { State } from '~/types/vuex'
import type { NuxtAuth } from '~/types/nuxt-auth'

/**
* Represents our extended Vue instance.
*
* We just use the annotations here, since properties are already injected.
* You will need to add new annotations in case you will extend Vue with new
* plugins.
*/
export default class CustomVue extends Vue {
  $auth: NuxtAuth
  $axios: Axios
  $router: Router
  $store: Store<State>
}

And then just using it like so:

// @flow

import Component from 'nuxt-class-component'

import CustomVue from '~/logics/custom-vue'

@Component()
export default class Index extends CustomVue {
  logout () {
    this.$auth.logout()  // type of `logout()` is defined in `NuxtAuth` 
  }
}

And it works!

@sobolevn thank you for the suggestion ! The only 'drawback' is now you have to import 'custom-vue' instead of directly importing 'vue', which might (?) cause issues in plugins relying on the import of the actual library.

In my case, I had to deal with 'react' and I think JSX would not be properly understood if I would rename the 'react' library itself.

What I have ended up doing (in case someone else come to this post looking for the same) is to wrap the required function within a 'typed' version of it. Fortunately, in my case the function I was willing to add is forwardRef from React, which is already in the pipeline to be added, so I could refer to the proposed typing from https://github.com/facebook/flow/issues/6103.

In my case the definition looks like:

// $FlowFixMe
import {forwardRef} from 'react';
export function typedForwardRef<Props, ElementType: React$ElementType>(
    render: (props: Props, ref: React$Ref<ElementType>) => React$Node
): React$ComponentType<Props> {
    return forwardRef(render);
}

I have meet this issue from the very first beginning of trying to introduce flow into my existing project,and this problem scares me away, and I do not like TypeScript too, so I think maybe I can only keep checking types using my eyes 🤷‍♂️

I've got a related use-case: I'd like to force all interactions with DOM APIs to enforce sanitization of untrusted content. I can declare opaque type safeHTMLString = string;, and create APIs that accept a string, process it, and return a safeHTMLString. The missing ingredient is overriding the DOM APIs to only accept safeHTMLString and fail on string. For instance, document.querySelector('body').innerHTML = someStringValue; should fail.

The only solution (actually dirty hack) advised so for (using --no-flowlib) does not work because the modules written this way can't be redistributed.
I think the team owes some explanation to the public why so stubbornly they refuse to move the libs to flow-typed

Maybe the core team members have moved to TypeScript?

I found creating new types using intersections to be a decent way to extend existing, thirdparty types.

I was trying to use react hooks in react-native but RN only support flow 0.78 currently, so I can not upgrade flow-bin.
I had to go with @AMongeMoreno solution.

I'm looking to create some typings for a testing library of mine, Must.js, that extends Object and other builtins' prototypes. Am I correct in saying that this is still impossible to perform with Flow without copying the entirety of https://github.com/facebook/flow/blob/master/lib/core.js to Must.js and adding a property that way? I do see mentions of intersection types and type extensions, yet if the rest of the codebase is typed to return Object, it doesn't do much if there's also an ExtendedObject that no-one refers to. Thanks!

Once again after more than a year I am looking into useful way to add strong typing ...

I just don't like TypesScript, and enabling intellisense for plain JS in VS code is not working properly yet
...... more than 4 years since this issue was opened, and nothing ...

Just try TypeScript. Evidently Microsoft cares more about community than Facebook.

It's now possible to override custom (user-land) types with the spread type operators. This is a huge win for me (finally!). Has anybody tried overriding built-in types this way?

@unscriptable What do you mean by "override custom type"? Spread creates new type like values spread creates new object. There is no mutating happen.

I think somebody needs to write a blog post about how spreads can be used in place of type extension. I can see a few use cases (listed in this thread) that I can’t figure out how a spread type would help. One way of formulating a motivating use case is this: Given a global type like MouseEvent, how might I use a spread so Flow doesn’t complain when I try to access a new property that is not yet added to the spec definition? And what if the MouseEvent is defined as a class rather than an object?

Was this page helpful?
0 / 5 - 0 ratings