fable-compiler / ts2fable

Parser of Typescript declaration files
http://fable.io/ts2fable/
Apache License 2.0
223 stars 34 forks source link

react-google-maps isn't properly generated. #227

Open nojaf opened 6 years ago

nojaf commented 6 years ago

Version: 0.6.1 Steps to reproduce:

Try and generate from react-google-maps/types/index.d.ts. For ex:

declare module 'react-google-maps/lib/components/GoogleMap' {
    import { Component } from 'react'

    export interface GoogleMapProps {
        defaultCenter?: google.maps.LatLng | google.maps.LatLngLiteral
        defaultClickableIcons?: boolean
        defaultHeading?: number
        defaultMapTypeId?: google.maps.MapTypeId | string
        defaultOptions?: google.maps.MapOptions
        defaultStreetView?: google.maps.StreetViewPanorama
        defaultTilt?: number
        defaultZoom?: number
        center?: google.maps.LatLng | google.maps.LatLngLiteral
        clickableIcons?: boolean
        heading?: number
        mapTypeId?: google.maps.MapTypeId | string
        options?: google.maps.MapOptions
        streetView?: google.maps.StreetViewPanorama
        tilt?: number
        zoom?: number

        onBoundsChanged?(): void
        onCenterChanged?(): void
        onClick?(e: google.maps.MouseEvent | google.maps.IconMouseEvent): void
        onDblClick?(e: google.maps.MouseEvent): void
        onDrag?(): void
        onDragEnd?(): void
        onDragStart?(): void
        onHeadingChanged?(): void
        onIdle?(): void
        onMapTypeIdChanged?(): void
        onMouseMove?(e: google.maps.MouseEvent): void
        onMouseOut?(e: google.maps.MouseEvent): void
        onMouseOver?(e: google.maps.MouseEvent): void
        onProjectionChanged?(): void
        onResize?(): void
        onRightClick?(e: google.maps.MouseEvent): void
        onTilesLoaded?(): void
        onTiltChanged?(): void
        onZoomChanged?(): void
    }

    export default class GoogleMap extends Component<GoogleMapProps> {
        fitBounds(bounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral): void
        panBy(x: number, y: number): void
        panTo(latLng: google.maps.LatLng | google.maps.LatLngLiteral): void
        panToBounds(latLngBounds: google.maps.LatLngBounds | google.maps.LatLngBoundsLiteral): void
        getBounds(): google.maps.LatLngBounds
        getCenter(): google.maps.LatLng
        getClickableIcons(): boolean
        getDiv(): Element
        getHeading(): number
        getMapTypeId(): google.maps.MapTypeId | string
        getProjection(): google.maps.Projection
        getStreetView(): google.maps.StreetViewPanorama
        getTilt(): number
        getZoom(): number
    }
}
module React_google_maps_lib_components_GoogleMap =
    type Component = React.Component

    type [<AllowNullLiteral>] IExports =
        abstract GoogleMap: GoogleMapStatic

    type [<AllowNullLiteral>] GoogleMapProps =
        abstract defaultCenter: U2<Google.Maps.LatLng, Google.Maps.LatLngLiteral> option with get, set
        abstract defaultClickableIcons: bool option with get, set
        abstract defaultHeading: float option with get, set
        abstract defaultMapTypeId: U2<Google.Maps.MapTypeId, string> option with get, set
        abstract defaultOptions: Google.Maps.MapOptions option with get, set
        abstract defaultStreetView: Google.Maps.StreetViewPanorama option with get, set
        abstract defaultTilt: float option with get, set
        abstract defaultZoom: float option with get, set
        abstract center: U2<Google.Maps.LatLng, Google.Maps.LatLngLiteral> option with get, set
        abstract clickableIcons: bool option with get, set
        abstract heading: float option with get, set
        abstract mapTypeId: U2<Google.Maps.MapTypeId, string> option with get, set
        abstract options: Google.Maps.MapOptions option with get, set
        abstract streetView: Google.Maps.StreetViewPanorama option with get, set
        abstract tilt: float option with get, set
        abstract zoom: float option with get, set
        abstract onBoundsChanged: unit -> unit
        abstract onCenterChanged: unit -> unit
        abstract onClick: e: U2<Google.Maps.MouseEvent, Google.Maps.IconMouseEvent> -> unit
        abstract onDblClick: e: Google.Maps.MouseEvent -> unit
        abstract onDrag: unit -> unit
        abstract onDragEnd: unit -> unit
        abstract onDragStart: unit -> unit
        abstract onHeadingChanged: unit -> unit
        abstract onIdle: unit -> unit
        abstract onMapTypeIdChanged: unit -> unit
        abstract onMouseMove: e: Google.Maps.MouseEvent -> unit
        abstract onMouseOut: e: Google.Maps.MouseEvent -> unit
        abstract onMouseOver: e: Google.Maps.MouseEvent -> unit
        abstract onProjectionChanged: unit -> unit
        abstract onResize: unit -> unit
        abstract onRightClick: e: Google.Maps.MouseEvent -> unit
        abstract onTilesLoaded: unit -> unit
        abstract onTiltChanged: unit -> unit
        abstract onZoomChanged: unit -> unit

    type [<AllowNullLiteral>] GoogleMap =
        inherit Component<GoogleMapProps>
        abstract fitBounds: bounds: U2<Google.Maps.LatLngBounds, Google.Maps.LatLngBoundsLiteral> -> unit
        abstract panBy: x: float * y: float -> unit
        abstract panTo: latLng: U2<Google.Maps.LatLng, Google.Maps.LatLngLiteral> -> unit
        abstract panToBounds: latLngBounds: U2<Google.Maps.LatLngBounds, Google.Maps.LatLngBoundsLiteral> -> unit
        abstract getBounds: unit -> Google.Maps.LatLngBounds
        abstract getCenter: unit -> Google.Maps.LatLng
        abstract getClickableIcons: unit -> bool
        abstract getDiv: unit -> Element
        abstract getHeading: unit -> float
        abstract getMapTypeId: unit -> U2<Google.Maps.MapTypeId, string>
        abstract getProjection: unit -> Google.Maps.Projection
        abstract getStreetView: unit -> Google.Maps.StreetViewPanorama
        abstract getTilt: unit -> float
        abstract getZoom: unit -> float

    type [<AllowNullLiteral>] GoogleMapStatic =
        [<Emit "new $0($1...)">] abstract Create: unit -> GoogleMap

import { Component } from 'react' should open the React namespace.

Changing React.Component to Fable.Import.React.Component causes The type 'Fable.Import.React.Component<_,_>' expects 2 type argument(s) but is given 0

Google.Maps.InfoWindowOptions, Google is not known. Should it not parse @types/googlemaps as well?

MangelMaxime commented 6 years ago

For @types/googlemaps, you need to pass it the file manually at the same time as react-google-maps

Please note, that ts2fable will generate only strongly typed React components not the DSL used in your view in general which is much more usable from Elmish and F#/Fable POV

nojaf commented 6 years ago

Passing @types/googlemaps indeed works. What about the React stuff?

Given the following TypeScript:

declare module 'react-google-maps/lib/withGoogleMap' {
    import { ComponentClass, ReactElement, StatelessComponent } from 'react'

    export interface WithGoogleMapProps {
        containerElement: ReactElement<any>
        mapElement: ReactElement<any>
    }

    export default function withGoogleMap<P>(wrappedComponent: string | ComponentClass<P> | StatelessComponent<P>): ComponentClass<P & WithGoogleMapProps>
}

Gets translated to image

Knowing that ReactElement looks like

    interface ReactElement<P> {
        type: string | ComponentClass<P> | SFC<P>;
        props: P;
        key: Key | null;
    }

But the Fable ReactElement interface is not generic.

type ComponentClass = React.ComponentClass

Could be solved by making

type ComponentClass<'a> = React.ComponentClass<'a>

Which makes sense given

interface ComponentClass<P = {}> extends StaticLifecycle<P, any> {
        new (props: P, context?: any): Component<P, ComponentState>;
        propTypes?: ValidationMap<P>;
        contextTypes?: ValidationMap<any>;
        childContextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
    }
humhei commented 6 years ago

There is another version of React.fs which is fully generated by ts2fable. I believe you can get more info by comparing these two versions

nojaf commented 6 years ago

Hmm everything else in the Fable landscape uses Fable.Import.React bindings. Should these not be fully compatible with the generated React.fs by ts2fable? /cc @alfonsogarciacaro

MangelMaxime commented 6 years ago

@nojaf In general, when working with react binding we use the DUs list with keyValueList style not class style. At least, when targetting Elmish because like that we have a consistant DSL for the views.

Edit: Even if ts2fable is doing a pretty good job, it's not yet ready to parse any d.ts files so for some libraries we don't use it by default yet. At least, it's my POV

alfonsogarciacaro commented 6 years ago

I generated the first Fable.Import.React bindings with one of the very first versions of ts2fable and then we did manual tweaking. But yes, it could be a good idea to update it to match the bindings generated with latest ts2fable to make sure we get the updates. We're about to release a new major Fable.React version for Fable 2 so it could be a good opportunity. We will still need some manual tweaking, specially for React.Component which currently has lots of documentation, some custom members, and default dummy implementations for abstract members users don't necessarily need to implement.