Definitelytyped: [D3] Refine Definitions/Technical Debt Reduction

Created on 13 Feb 2018  Β·  45Comments  Β·  Source: DefinitelyTyped/DefinitelyTyped

  • [x] [Mention](https://github.com/blog/821-mention-somebody-they-re-notified) the authors (see Definitions by: in index.d.ts) so they can respond.

    • Authors: @tomwanzek @gustavderdrache @Ledragon

I am creating this issue as a replacement tracking issue for #11365, #11365 and #17846.

The following is a table to track refinements/technical debt related to D3 module definitions.

  • JSDoc: Complete JSDoc comments including parameters and generics explanation
  • strictNullChecks: Validated for strictNullChecks and compiler option set to true
  • strictFunctionTypes: Validated for strictFunctionTypes and compiler option set to true
  • TS 2.3: Minimum version of TS 2.3 and definitions use defaults for generics

| Definition| JSDoc | strictNullChecks | strictFunctionTypes | TS 2.3 |
| --- | --- | --- | --- | --- |
| d3 | N/A | πŸ”² | πŸ”² | βœ… |
| d3-array | πŸ”² | βœ… | πŸ”² | πŸ”² |
| d3-axis | βœ… | βœ… | βœ… | βœ… |
| d3-brush | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-chord | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-collection | βœ… | βœ… | βœ… | βœ… |
| d3-color | πŸ”² | βœ… | βœ… | βœ… |
| d3-contour | βœ… | βœ… | βœ… | πŸ”² |
| d3-dispatch | βœ… | βœ… | βœ… | βœ… |
| d3-drag | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-dsv | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-ease | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-fetch | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-force | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-format | βœ… | βœ… | βœ… | βœ… |
| d3-geo | βœ… | βœ… | βœ… | βœ… |
| d3-hexbin | πŸ”² | πŸ”² | πŸ”² | πŸ”² |
| d3-hierarchy | πŸ”² | πŸ”² | πŸ”² | πŸ”² |
| d3-interpolate | πŸ”² | πŸ”² | πŸ”² | πŸ”² |
| d3-path | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-polygon | βœ… | βœ… | βœ… | βœ… |
| d3-quadtree | πŸ”² | πŸ”² | πŸ”² | πŸ”² |
| d3-queue | βœ… | πŸ”² | πŸ”² | πŸ”² |
| d3-random | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-request | πŸ”² | πŸ”² | πŸ”² | πŸ”² |
| d3-sankey | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-scale | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-scale-chromatic | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-selection | βœ… | βœ… | βœ… | πŸ”² |
| d3-selection-multi | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-shape | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-time | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-time-format | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-timer | βœ… | πŸ”² | πŸ”² | πŸ”² |
| d3-transition | βœ… | βœ… | πŸ”² | πŸ”² |
| d3-voronoi | βœ… | πŸ”² | πŸ”² | πŸ”² |
| d3-zoom | βœ… | βœ… | πŸ”² | πŸ”² |

"Outside" of core team maintenance:

| Module | JSDoc | strictNullChecks | strictFunctionTypes | TS 2.3 |
| --- | --- | --- | --- | --- |
| d3-hsv | βœ… | βœ… | βœ… | βœ… |

Most helpful comment

@denisname πŸ’― @gustavderdrache @ledragon Thanks for all the hard work over the past little while. I updated the tracking table, looks so much prettier already! 'cause a pretty tracking table is what we are aiming for here :smile:

All 45 comments

@gustavderdrache @Ledragon Above I consolidated the few outstanding tasks to round of the oeuvre that is the set of D3 definitions.

A key question: Do we want to upgrade all definitions consistently to a TS >=2.3 constraint and in the process remove some of the functions/method overloads by using default assignments for generics?
I noticed that (inadvertently) some of the definitions already have the // TypeScript Version: 2.3 constraint in the definitions header. E.g. d3-geo, which uses the geoJson definitions. These already use generic defaults.

Your thoughts on this matter would be more than welcome.

Entender esto hay que estudiarlo

If implementing a TS 2.3 minimum, we should also be able to use the object type instead of any, where appropriate. Came with TS 2.2. If I recall correctly, there were a handful of opportunities with object interpolators and in d3-collection.

First thoughts:

  • Due to the out-of-memory bug you encountered, should we not enforce 2.4 everywhere?
  • I have a branch with strictNullChecks and strictFunctionTypes here. I will update it and submit a PR. I thought I did, but it does not seem so

I have to learn about default generics before I have an opinion on this :p

With TS 2.3 we are looking at a release from back in April 2017.
TS 2.4 was released June 2017.

So the big question is: are we creating issues in a large or negligible/no part of the installed base when forcing a minimum of 2.4 (rather than 2.3)?

I guess one of the added challenges is that we don't have a Changelog per se to flag potentially breaking changes to how the D3 definitions are consumed. And the obvious disconnect, that a breaking change could occur in a minor version release of the definitions, since they are aligned with the underlying D3 release cycle.

@gustavderdrache What's your take on TS 2.4 as the new minimum?

As seen in PR #23654, we appear to cascade quickly when going to TS 2.4 (even if it is just imposing the constraint for DT's sake without using any TS 2.4 features like string enums).

For clarity as per the new PR #23724 we can proceed with simply using TS 2.3. No need to push forward with TS 2.4 as of now.

@Ledragon If you want to open the PR you already had pending in your local d3-geo fork, then we can work through to check off the boxes above.

I actually can't get it testing locally... So I think I can submit it, but I don't think it will pass the travis tests. I'll just go ahead and see

23794 submitted - not my proudest work, let's see how it goes...

Okay, so the problem is as follows: d3-geo tests fail in TS 2.3, so I tried setting the version to 2.4. However, d3 global is set to TS 2.3, so that does not work either. Any advice on how to proceed?

I'll look at the g3-geo PR and will put any review comments there to keep them focused. Unlike with the OoM bug I had with d3-collection, we have a proper error message to work with πŸ˜„

Just submitted #24118 to update d3-contour to 1.2.0
I noticed that d3-contour types are already in TS2.3, and have strictNullChecks and strictFunctionTypes set to true :-)

Thanks for staying on top of d3-contour, made me notice that for some odd reason I did not have the repo on "Watching". Changed that! :smile:

Will look at the PR shortly.

I am working on d3-axis and d3-format. I'm almost done. But have some questions...

In d3-format I want to use the same Numeric interface than in d3-array:

interface Numeric {
    valueOf(): number;
}

But when doing this, in the d3 global definitions, I logically have the error: Module 'd3-array' has already exported a member named 'Numeric'. Is there a place to put shared types for d3 libraries?

Also in some d3 definitions (interpolate, scale, shape) you can find the union type number | { valueOf(): number }. Is { valueOf(): number } not enough?

@denisname Thanks for volunteering for d3-axis, d3-format and later d3-array !!!

To your questions above:

(1) As a fundamental rule for writing the definitions for d3-xxx modules, the definitions must never introduce dependencies that do not exist among the underlying corresponding D3 modules. E.g. d3-axis has no dependency on d3-array, therefore, the definition index.ts file for d3-axis must not import from d3-array. This ensures loose coupling corresponding to the JS sources. So in case of doubt check the dependencies property of the underlying D3 module's package.json _Note: You can of course import from any package in the d3-xxx-test.ts file, this is even a good practice we followed in a number of packages for "integration" testing. I.e. there may be no formal dependency between two packages, but members of one are "naturally" used a input for the other. E.g. we are using d3-selection in a test in d3-chord to ensure a chord path can be passed into a selection attribute setter without issues._

(2) You are correct, that the Numeric interface cannot be re-declared in any other D3 module, which cannot import from d3-array in compliance with rule (1).

(3) Is { valueOf(): number } not enough? Technically, yes. Practically, the intersection type, while having some redundancy, is declaratively probably clearer for a lot of human users. I.e. it shows that number is a valid type at first glance without mental acrobatics. :wink:

About d3-color, why are the prototype all commented out? It has been done in this commit by @tomwanzek.

With the prototypes set back, we could use instanceof directly, without the need for a typeguard function:

let cRGB: d3.RGBColor;
let cHSL: d3.HSLColor;

const c: d3.RGBColor | d3.HSLColor = d3.color("steelblue");

if (c instanceof d3.rgb) {
    cRGB = c;
} else {
    cHSL = c;
}

@denisname I have been hesitant to define prototype as part of the published API in an interface, it feels too hacky.

For what I understand from today's type guards spec. This is now considered as a valid _construction_. See the list item:

A type guard of the form x instanceof C, where x is not of type Any, C is of a subtype of the global type 'Function', and C has a property named 'prototype' [...]

I would like to propose a strictNullChecks version of d3-color. Which is just a one line change. This would be an opportunity to add prototype too.

From my own testing, you need either the prototype property or a new(): T declaration for instanceof to properly narrow the type. I used the new(): Color variation in the v3 typings, and it might be useful if that idiom is still supported by D3v4 and above.

As either one seems fine, I think we might follow the v3 convention for continuity. Great work, both.

@gustavderdrache

My understanding of why it works in d3 v3 is that the return type of new() is always the same as the prototype type. But in d3 v4 we also have:

export const color: ColorFactory;
export interface ColorFactory extends Function {
    (cssColorSpecifier: string): RGBColor | HSLColor;
    prototype: Color; // and not RGBColor | HSLColor !
}

Indeed, d3.lab(0,0,0) instanceof d3.color is true. Hence, if we change this interface to:

export const color: ColorFactory;
export interface ColorFactory extends Function {
    (cssColorSpecifier: string): RGBColor | HSLColor;
    new (cssColorSpecifier: string): RGBColor | HSLColor;
}

We don't have as prototype for ColorFactory the type Color. And the following code fail to compile, while it should not.

declare let l: d3.LabColor | null;
declare let c: d3.Color;
declare let nil: null;

if (l instanceof d3.color) {
    c = l; // l is not inferred as `d3.LabColor`...
} else {
    nil = l; // fail, l is a `d3.LabColor | null` should be a `null`
}

What is your opinion? Is there a way to make it work with new? Thanks.

It looks like the prototype property for color() should be Color and not RGBColor | HSLColor, based on some testing:

> d3.color.prototype === d3.rgb.prototype
false
> d3.rgb.prototype instanceof d3.color
true

The color() function returns RGB or HSL colors, but its prototype is a supertype of the other color spaces.

@denisname @Ledragon @gustavderdrache since you are all on this thread, just as a brief FYI: I intend to catch up on the open items which you are aware of this weekend.

Alright, d3-geo is out the door (thanks @ledragon) and I commented on @denisname 's unfortunately closed PR for d3-format and d3-axis regarding re-opening.

As a general note, I would recommend @denisname be added as another maintainer to definitions, they work on.

I might have a look at d3-color next, and join it with an update to 1.1.0. Should we open a separate issue for this upgrade?

Also, welcome on board @denisname !

@Ledragon Thanks.

I have an update ready for d3-color (no lhc and gray yet). I'm just stuck by one little problem.

@gustavderdrache said:

The color() function returns RGB or HSL colors, but its prototype is a supertype of the other color spaces.

Indeed and this can be _typed_ easily, see the first interface in my comment. But @tomwanzek proposed to use only new() and avoid prototype. I think that in this particular case it is not possible. No?...

After playing with it some more, I'm seeing the problem. You can set new(): Color in the ColorConstructor interface, but it doesn't actually cover the return value of the function:

declare namespace d3 {
  interface Color {
    // I forgot what was on the Color interface
    // This property exists just to make Color incompatible with {}
    __isColor: never;
    toString(): string;
  }

  interface RGBColor extends Color {
    r: number;
    g: number;
    b: number;
  }

  interface HSLColor extends Color {
    h: number;
    s: number;
    l: number;
  }

  interface LABColor extends Color {
    l: number;
    a: number;
    b: number;
  }

  interface ColorConstructor {
    (specifier: string): RGBColor | HSLColor;
    new(specifier: string): Color; // <- TS uses this for narrowing with instanceof
  }

  const color: ColorConstructor;
}

declare let l: d3.LABColor | null;
declare let c: d3.Color;
declare let nil: null;

if (l instanceof d3.color) {
  // Succeeds with l: d3.LABColor
  c = l;
} else {
  // Succeeds with l: null
  nil = l;
}

In conclusion: I think we have to expose the prototype property, because it's really the only way to cover the correct behavior of both the constructor and instanceof testing:

  interface ColorConstructor {
    (specifier: string): RGBColor | HSLColor;
    new(specifier: string): RGBColor | HSLColor;

    readonly prototype: Color; 
  }

Sorry, for the delay in getting back to this. Feel free to go with prototype, back in the day, it was my first impulse to address the instanceof as well. It just "felt" hacky, but as it is considered an acceptable practice, and if continuity with using new() as in D3v3 is not an option...Let's go with what works!

@tomwanzek could you update the tracking table.

An βœ… should be set in columns strictNullChecks strictFunctionTypes and TS 2.3 for libraries d3-axis, d3-color, d3-dispatch, d3-format, d3-polygon and d3-hsv.

Also an βœ… should be set in column JSDoc for d3-dispatch, d3-polygon and d3-hsv.

Thanks

There is a typo in d3-geointerface GeoIdentityTranform. It should be GeoIdentityTransform (with a s). May I correct it? Any concerns about backward compatibility?

@denisname for d3-geos GeoIdentityTranform, I think we could do the following:

  • Rename the misspelled interface (Great catch!) (including its use as a return type of geoIdentity()
  • For the time being add
/**
 * @deprecated Misspelled name. Use GeoIdentityTransform.
 */
export type GeoIdentityTranform = GeoIdentityTransform;
  • At a later convenient stage remove the misspelled type altogether.

@denisname πŸ’― @gustavderdrache @ledragon Thanks for all the hard work over the past little while. I updated the tracking table, looks so much prettier already! 'cause a pretty tracking table is what we are aiming for here :smile:

'cause a pretty tracking table is what we are aiming for here

Absolutely! The improved type definitions are just a pleasant side effect.

Are any of you working on specific definitions right now for completion of the technical debt? While d3-array is in process. I would tackle the strictFunctionTypes in d3-transition next to bring it to parity with d3-selection. Just waiting for #25805 to be merged.

Not ATM. Will let y'all know if and when I do

Working on #25582 to be able to stamp the 5.2.0 global bundle

I have an update for d3-hierarchy ready (strict and jsDoc).
Also working on d3-dsv and d3-fetch (ts 2.3).

@denisname @tomwanzek @gustavderdrache
Should we focus on this, or on updating d3 to the latest version? We are now 5 minor versions behind on the global package (though all sub-packages are ready for 5.2.0 I think). Shall I open a separate issue for tracking the global package status?

@Ledragon I will catch up with the open PRs tonight and parse all D3 module definitions for currency. If there are any lags, I will create one tracking issue to bring them up to par. As for priorities, I agree that currency should take priority over technical debt reduction.

Sorry to pollute this thread, I'm getting back into D3.js now for a new project.. and I was wondering if having inline TypeScript annotation was considered for D3?

/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),

In webpack, they started using it to check JavaScript with the TypeScript compiler.. big plus is the typing definitions live right next to the actual code.
https://github.com/webpack/webpack/blob/master/lib/Compiler.js#L51

Hi @phil-lgr
There was a discussion on the d3 issue tracker not so long ago, and it did not seem to be on the top of the priority list (i.e. Mike Bostock prefered focusing on developing the library itself rather than the typings). I can't seem to find a link to the thread. Maybe the question can be raised again thanks to this new information, but I don't think it likely to happen

@tomwanzek could you update the tracking table.

An βœ… should be set in columns strictNullChecks strictFunctionTypes and TS 2.3 for libraries d3-array, d3-array, d3-dsv, d3-fetch, d3-hexbin, d3-hierarchy, d3-interpolate, d3-quadtree, d3-queue, d3-request, d3-timer and d3-voronoi.

Also an βœ… should be set in column JSDoc for d3-color, d3-hexbin,d3-hierarchy, d3-interpolate and d3-quadtree.

Thanks

Was this page helpful?
0 / 5 - 0 ratings