Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Types By Refactoring JSDoc #397

Merged
merged 20 commits into from
Mar 18, 2021
Merged

Conversation

ilan-gold
Copy link
Collaborator

@ilan-gold ilan-gold commented Mar 17, 2021

Background

For #346 (comment) this adds a constructor and also types for the constructor's arguments (which are the layer props).

@andreasg123 I believe this should resolve that issue. Thanks for mentioning it!

Opening this as a draft while waiting on visgl/deck.gl#5587

Question:

  • Is it worth making a base LayerProps typedef for all the layers to share that includes the basics like channelIsOn, opacity etc.?

I think this fixes #342

Change List

  • Add constructors to the layer classes.
  • Change where docs exist on layers to constructor so that TypeScript definition includes constructor and its arguments

Checklist

  • Update JSdoc types if there is any API change.
  • Make sure Avivator works as expected with your change.

@ilan-gold ilan-gold changed the title Change to constructor with types. Change to Layer constructor with types. Mar 17, 2021
@ilan-gold ilan-gold requested review from manzt and removed request for manzt March 17, 2021 21:37
@ilan-gold ilan-gold marked this pull request as ready for review March 17, 2021 21:52
@ilan-gold ilan-gold requested a review from manzt March 17, 2021 21:58
Comment on lines 136 to 140
constructor(props) {
// needed for TypeScript types that are generated from the JSDoc
// eslint-disable-next-line prefer-rest-params
super(...arguments);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't love this. Typescript and JSDoc should just disappear from the final JS. Unfortunately this is actually changing our JavaScript code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tried it in my own code but you are changing the constructor signature of CompositeLayer here: https://deck.gl/docs/api-reference/core/composite-layer#constructor-1
It's supposed to accept any number of props objects that are combined by Object.assign (or something similar).

Copy link
Member

@manzt manzt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big fan of the solution, but it works and am not aware of alternatives and it seems to tick the box for what we need.

A question and a comment:

1.) Have you check what the new generated documentation end up looking like? I've found that changing JSDoc usage can effect that output in unexpected ways.

2.) I wouldn't worry about some type of prop inheritance for now. If its something you're keen to explore, you can always create a types.d.ts and import types in jsdoc:

export interface MyInterface {
  foo: string;
  bar: number;
}
/**
 * @param {import('./types').MyInterface} props
 */
function fn(props) {
  /* ... */
}

I tried doing this for class definitions but couldn't figure it out. The other issue is that could lose some of the documentation comments which are more valuable. My hope was just to have:

/**
 * @type {import('./types').MultiscaleImageLayer}
 */
class MultiScaleImageLayer extends MultiscaleImageLayerBase 
 /* ... */
}

@ilan-gold
Copy link
Collaborator Author

1.) Have you check what the new generated documentation end up looking like? I've found that changing JSDoc usage can effect that output in unexpected ways.

Yes it did not change much - it's a little bit "wrong" but I think people will understand what is going on:
Now:
Screen Shot 2021-03-17 at 6 39 48 PM
Before:
Screen Shot 2021-03-17 at 6 39 43 PM

@manzt
Copy link
Member

manzt commented Mar 17, 2021

Yeah, certainly a tradeoff though.

I notice it says props: any now. Do you get code-completion in VSCode with a build of Viv?

@ilan-gold
Copy link
Collaborator Author

ilan-gold commented Mar 18, 2021

Yeah, certainly a tradeoff though.

I notice it says props: any now. Do you get code-completion in VSCode with a build of Viv?

It would appear so, although not sure what it's supposed to look like:
Screen Shot 2021-03-17 at 8 33 27 PM

@manzt
Copy link
Member

manzt commented Mar 18, 2021

Not sure I know what I'm looking at for the screenshot you've posted. I built the project locally and have the following:

image

Which seems reasonable, but there are two outstanding issues:

1.) The fields defined on LayerProps aren't optional, so typescript won't compile the code. You can check this in JS by just adding // @ts-check to the top of your file importing one of our layer classes and providing an empty object as an argument:

image

To make optional types in JSDoc is done by adding a = within the curly braces for a type.

e.g. @property {number=} opacity Opacity of the layer.

2.) As @andreasg123 pointed out, the constructor definition doesn't actually match what deck.gl allows. In its current form, the constructor type definition says it takes one argument (of LayerProps shape) and not multiple props objects (which deck.gl allows). We do some magic "spreading" of arguments to make sure the pattern of passing multiple args works, but that isn't captured by the type system. My suggestion is that you remove the constructor signature for the following:

/**
 * @type {{ new (...props: LayerProps[]) }}
 */
const ImageLayer = class extends CompositeLayer { /* ... */ }
ImageLayer.layerName = 'ImageLayer';
ImageLayer.defaultProps = defaultProps;
export default ImageLayer;

Unfortunately export default const ... isn't valid syntax, so we'll need to change the export slightly, but otherwise all changes to the source are just in the comments. Does this seem reasonable to you, @andreasg123 ?

@manzt
Copy link
Member

manzt commented Mar 18, 2021

BTW, you can extract the LayerProps type for a particular layer using the ConstructorParameters utility type:

image

Unfortunately JSDoc annotations for objects more complex than JS primitive types (e.g. Array) aren't very expressive. We can probably use TS syntax instead for some of these props:

JSDoc annotations

/**
 * @typedef LayerProps
 * @type {Object}
 * @property {Array} sliderValues List of [begin, end] values to control each channel's ramp function.
 * @property {Array} colorValues List of [r, g, b] values for each channel.
 * @property {Array} channelIsOn List of boolean values for each channel for whether or not it is visible.
 * @property {number} opacity Opacity of the layer.
 * @property {string} colormap String indicating a colormap (default: '').  The full list of options is here: https://github.com/glslify/glsl-colormap#glsl-colormap
 * @property {Array} domain Override for the possible max/min values (i.e something different than 65535 for uint16/'<u2').
 * @property {string} viewportId Id for the current view.  This needs to match the viewState id in deck.gl and is necessary for the lens.
 * @property {Object} loader PixelSource. Represents an N-dimensional image.
 */

TS syntax

/**
 * @typedef LayerProps
 * @type {Object}
 * @property {[start: number, end: number][]} sliderValues List of [begin, end] values to control each channel's ramp function.
 * @property {[r: number, b: number, g: number][]} colorValues List of [r, g, b] values for each channel.
 * @property {boolean[]} channelIsOn List of boolean values for each channel for whether or not it is visible.
 * @property {number} opacity Opacity of the layer.
 * @property {string} colormap String indicating a colormap (default: '').  The full list of options is here: https://github.com/glslify/glsl-colormap#glsl-colormap
 * @property {[min: number, max: number][]} domain Override for the possible max/min values (i.e something different than 65535 for uint16/'<u2').
 * @property {string} viewportId Id for the current view.  This needs to match the viewState id in deck.gl and is necessary for the lens.
 * @property {import('../types').PixelSource<string[]>} loader PixelSource. Represents an N-dimensional image.
 */

Which results in:

image

The main benefit here is that our types are much more precise, and by importing the PixelSource type, we have an explicit link from our loaders to our layers.

EDIT: @ilan-gold, hmmm but this breaks our documentation generation with documentation-js (the TS syntax is ignored). Maybe we could break this into two PRs. 1.) Get current JSDoc annotations linked to constructor, 2.) Use TS in JSDoc for more precise types and make sure docs work.

@ilan-gold
Copy link
Collaborator Author

@manzt I think I was able to add typed arrays to the JSDoc interfaces:
Screen Shot 2021-03-18 at 10 30 00 AM
Screen Shot 2021-03-18 at 10 29 51 AM

Does this look right?

The other thing I was able to do was to fix the array spread business @andreasg123 brought up via:
Screen Shot 2021-03-18 at 10 32 09 AM
which yields what appears to be the correct type:
Screen Shot 2021-03-18 at 10 32 35 AM

Does this address this issues? Maybe I am missing something, and I know the second solution is a little different, but it seems to work.

@ilan-gold
Copy link
Collaborator Author

For example for the array of props issue, this does seem to work:
Screen Shot 2021-03-18 at 10 41 20 AM

@ilan-gold
Copy link
Collaborator Author

The docs don't respect "the array of props" still but we are no worse off there than before.

@manzt
Copy link
Member

manzt commented Mar 18, 2021

Yeah I think those annotations from JSDoc are much better! They still aren't quite as precise as what TS syntax offers (e.g. an array of fixed length tuples is more strict that an array of variable length arrays), but slightly more strict which is great.

For example,

const layer = new Layer({ 
  domain: [[1,2,34], [2,3], [1,2,3,34,5,6,7]], // this ok with `Array.Array<number>` and we know it's not.
  /* ... */
 });

Second, it would be my preference to remove the addition of the "magic" constructor in favor of using JSDoc @type annotation as I suggested.

/**
 * @type {{ new (...props: LayerProps[]) }}
 */
const ImageLayer = class extends CompositeLayer { /* ... */ }
ImageLayer.layerName = 'ImageLayer';
ImageLayer.defaultProps = defaultProps;
export default ImageLayer;

We don't need to explain ourselves for every class and disable linting. This method just changes how the objects are exported.

@ilan-gold
Copy link
Collaborator Author

Second, it would be my preference to remove the addition of the "magic" constructor in favor of using JSDoc @type annotation as I suggested.

/**
 * @type {{ new (...props: LayerProps[]) }}
 */
const ImageLayer = class extends CompositeLayer { /* ... */ }
ImageLayer.layerName = 'ImageLayer';
ImageLayer.defaultProps = defaultProps;
export default ImageLayer;

We don't need to explain ourselves for every class and disable linting. This method just changes how the objects are exported.

Forgot about this, let me try it now!

* @property {number=} opacity Opacity of the layer.
* @property {function=} onClick Hook function from deck.gl to handle clicked-on objects.
* @property {Object=} modelMatrix Math.gl Matrix4 object containing an affine transformation to be applied to the image.
* @property {Array.<number>=} transparentColor An RGB (0-255 range) color to be considered "transparent" if provided.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

photometricInterpretation?

Copy link
Member

@manzt manzt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work Ilan! Thanks for going through all that with me. Hopefully this will make our end-users happier :)

Comment on lines 47 to 48
* @property {Array} loader PixelSource. Represents an N-dimensional image.
* @property {Array} loader Selection to be used for fetching data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd prefer if the the "required" fields proceeded all optional props.

* @property {number=} lensBorderRadius Percentage of the radius of the lens for a border (default 0.02).
* @property {number} dtype Dtype for the layer.
* @property {function=} onClick Hook function from deck.gl to handle clicked-on objects.
* @property {Object} modelMatrix Math.gl Matrix4 object containing an affine transformation to be applied to the image.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Listed as a required fielded; don't see where this gets used. Could be missing something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I think the push was rejected. My internet has been flaking in and out. Switched to hotspot, will try again.

CHANGELOG.md Outdated
@@ -4,6 +4,7 @@

### Added

- Add constructors to layer classes so that types are emitted properly.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit. We technically don't "emit" any types from our JSDoc annotations. They just remain in the bundled dist/index.js as comments. Maybe "Refactor JSDoc comments to properly annotate Layer classes with constructor signatures."

@ilan-gold
Copy link
Collaborator Author

No, thank you @manzt for pushing to make this perfect (and @andreasg123!) - this seems well worth the effort considering that we now have very nice types and internal accountability for the docs.

@ilan-gold ilan-gold changed the title Change to Layer constructor with types. Fix Types By Refactoring JSDoc Mar 18, 2021
@ilan-gold ilan-gold merged commit ab57189 into master Mar 18, 2021
@ilan-gold ilan-gold deleted the ilan-gold/add_constructor_types branch March 18, 2021 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

TypeScript declarations
3 participants