Definitions by:
in index.d.ts
) so they can respond.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.
strictNullChecks
and compiler option set to true
strictFunctionTypes
and compiler option set to true
| 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 | β
| β
| β
| β
|
@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:
strictNullChecks
and strictFunctionTypes
here. I will update it and submit a PR. I thought I did, but it does not seem soI 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
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', andC
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-geo
interface GeoIdentityTranform
. It should be GeoIdentityTransform
(with a s
). May I correct it? Any concerns about backward compatibility?
@denisname for d3-geo
s GeoIdentityTranform
, I think we could do the following:
geoIdentity()
/**
* @deprecated Misspelled name. Use GeoIdentityTransform.
*/
export type GeoIdentityTranform = GeoIdentityTransform;
@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
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: