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

Improve type hints to support advanced code navigation and assistance in IDEs (Intellij, VSCode) and LSPs #1002

Closed
6 tasks
philippfromme opened this issue Apr 26, 2019 · 20 comments
Labels
documentation Improvements or additions to documentation enhancement New feature or request

Comments

@philippfromme
Copy link
Contributor

philippfromme commented Apr 26, 2019

What should we do

We should fix the JSDoc type definitions across our projects and leverage TypeScript's support for JavaScript projects.

What we should not do

We should not, as of today, migrate our projects to TypeScript:

  • Type checking JavaScript projects and correct JSDoc on JavaScript projects provides equal capabilities and DX on the consumer side.
  • Not transpiling our code base during development (and run) is a major benefit.
@philippfromme philippfromme added the enhancement New feature or request label Apr 26, 2019
@nikku nikku changed the title Leverage JSDoc for Better Developer Experience in VS Code Leverage JSDoc for to provide type hints and advanced code navigation support in IDEs (Intellij, VSCode) Apr 26, 2019
@nikku nikku changed the title Leverage JSDoc for to provide type hints and advanced code navigation support in IDEs (Intellij, VSCode) Improve type hints to support advanced code navigation and assistance in IDEs (Intellij, VSCode) Apr 26, 2019
@nikku
Copy link
Member

nikku commented Apr 26, 2019

@philippfromme I've commented with a few more findings.

@barmac Would be amazing if you could fill in the TypeScript blanks 😉.

@philippfromme philippfromme added the backlog Queued in backlog label May 6, 2019
@lppedd
Copy link

lppedd commented May 14, 2019

@nikku I'll work with this project using Typescript, thus I will most likely produce type definitions for it.
There is currently a request for it here.

My opinion is that you should move everything to Typescript incrementally. I've noticed it really gives a boost to code quality and organization.

Also, as a user of IDEA/WebStorm, I've yet to find a better tooling, it's amazing, no joke.

@nikku
Copy link
Member

nikku commented May 16, 2019

@lppedd Thanks for sharing your feedback.

Just to manage expectations: We cannot promise any move to TypeScript any time soon 😉 .

@nikku
Copy link
Member

nikku commented May 16, 2019

As mentioned in the issue text, there exists other solution approaches that will likely give you the same amount of type hints without the need for us to move fully to TypeScript. We'd appreciate your help improving the typed support.

@lppedd
Copy link

lppedd commented May 22, 2019

@nikku I almost completed typings for BpmnJS and EventBus. I would just love to have more documentation on the objects returned to the callback functions, which seems to change between event types.

@lppedd
Copy link

lppedd commented May 30, 2019

@nikku @philippfromme here you can find an initial attempt at typings. I've done a bit of everything, but still I'm losing a lot of time debugging trying to understand objects' structures. If you could give a look and comment with some suggestion, it would be awesome!

The nice thing about the declarations is that didi instances are typed, e.g.

const elementRegistry = this.bpmn.get('elementRegistry');
      ^ type is ElementRegistry

ElementRegistry is then "fully" typed

export default class ElementRegistry {
  constructor(eventBus: EventBus);
  add(element: Base, gfx: SVGElement, secondaryGfx?: SVGElement): void;
  remove(element: string | Base): void;
  updateId(element: string | Base, newId: string): void;
  get(filter: string | SVGElement): Base;
  filter(fn: (element: Base, gfx: SVGElement) => boolean): Base[];
  getAll(): Base[];
  forEach(fn: (element: Base, gfx: SVGElement) => any): void;
  getGraphics(filter: string | Base, secondary?: boolean): SVGElement;
}

@nikku
Copy link
Member

nikku commented May 30, 2019

I like this one in particular that implements your example.

@lppedd
Copy link

lppedd commented May 30, 2019

@nikku yeah, that was a good idea! The same concept is applied also to Moddle#create here.
Example:

const startEvent = moddle.create('bpmn:StartEvent');
      ^ type is BpmnStartEvent

const extensionElements= moddle.create('bpmn:ExtensionElements');
      ^ type is BpmnExtensionElements

I've provided "defaults" for bpmn-js, but a user can extend those typings by simply redefining BpmnElementMap (here).
These means I can do, in my module

declare module 'moddle/lib/moddle' {
   interface BpmnElementMap {
      'myns:CustomElement': MyCustomElement;
   }
}

and then, in implementation code

const myElement = moddle.create('myns:CustomElement', { ... });
      ^ type is MyCustomElement

const definitions = moddle.create('bpmn:Definitions', { ... });
      ^ type is BpmnDefinitions

And if no match

const whatever = moddle.create('whatever');
      ^ type is simply ModdleElement

What I'm not sure about is the ModdleElement hierarchy here.
I think I'm doing something wrong.
What I'm trying to do is not use the any type as much as possible, because that's just like plain JS.

@lppedd
Copy link

lppedd commented May 31, 2019

That's how I implemented didi injection. I throw this in here because it might be useful for future reference.

@Inject(
  'eventBus',
  'styles',
  'pathMap',
  'canvas',
  'textRenderer'
)
class CustomRenderer extends BpmnRenderer {
  constructor(
    eventBus: EventBus,
    ...

The @Inject decorator is pretty simple

export function Inject(injection: string, ...others: string[]) {
  return <T extends Ctor>(ctor: T): void => {
    ctor.$inject = [injection, ...others];
  };
}

Which is basically what you'd do with

CustomRenderer.$inject = [
  'eventBus',
  'styles',
  'pathMap',
  'canvas',
  'textRenderer'
];

Just a lot cleaner for TS.

@yfwz100
Copy link

yfwz100 commented Jun 18, 2019

This is awesome! Is it planned to release in npm?

@nikku
Copy link
Member

nikku commented Jun 18, 2019

Nothing planned here.

@patoncrispy
Copy link

It would be great to see detailed API documentation and/or TypeScript added to this project. It is quite a lot of effort to find out what properties certain objects have (contexts, elements, events, etc.) what events are available, what parts of the system respond to which events, what the 'best practices' are for using/overriding/creating modules, what modules do, among other things.

This would be particularly useful when breaking changes are introduced, as while there is a changelog, type safety really does help the developer experience.

I would be happy to help out with at least the TypeScript effort if there is to be one.

@lppedd
Copy link

lppedd commented Jan 8, 2020

@nikku Hi! I've made some progress with typings. I'd like some help with this declaration, the context parameter.
https://github.com/lppedd/bpmnio-typings/blob/cc78174f2ff610b9d410928d1f2be779ec431f8b/src/%40types/diagram-js/index.d.ts#L1226

@lppedd
Copy link

lppedd commented Jan 8, 2020

@patoncrispy If you want you can PR here https://github.com/lppedd/bpmnio-typings for now.
I'd like to move it to DefinitelyTyped but it's still early imho.

@patoncrispy
Copy link

Cool thanks @lppedd I'll take a look soon.

@patoncrispy
Copy link

Hey @lppedd what more work do you think would need to be done to get your typings work into DefinitelyTyped?

@lppedd
Copy link

lppedd commented Jul 22, 2020

@patoncrispy it has been months I haven't worked on TS/JS projects, but I can assure you what I have done is a small portion of the framework. There is much much more

@nikku
Copy link
Member

nikku commented Nov 27, 2020

For historical purposes, this is what we've found in the past:


Option 1: Leverage TypeScript foundations

...

Option 2: Leverage existing JSDoc annotations

Right now we have a quite comprehensive set of existing type definitions, baked into our JSDoc statements. One example from our code base (Viewer#importXML):

/**
 * Parse and render a BPMN 2.0 diagram.
 *
 * Once finished the viewer reports back the result to the
 * provided callback function with (err, warnings).
 *
 * ## Life-Cycle Events
 *
 * During import the viewer will fire life-cycle events:
 *
 *   * import.parse.start (about to read model from xml)
 *   * import.parse.complete (model read; may have worked or not)
 *   * import.render.start (graphical import start)
 *   * import.render.complete (graphical import finished)
 *   * import.done (everything done)
 *
 * You can use these events to hook into the life-cycle.
 *
 * @param {String} xml the BPMN 2.0 xml
 * @param {ModdleElement<BPMNDiagram>|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
 * @param {Function} [done] invoked with (err, warnings=[])
 */
Viewer.prototype.importXML = function(xml, bpmnDiagram, done) {
...

This allows VSCode to provide decent hints already. It does not know about ModdleElement though, as we do not declare it.

Idea: Create type definitions for all types we refer to

By providing the corresponding JSDoc type definitions we can improve the existing developer experience. Given the following typings:

typeDefs.js

/**
 * @typedef Bounds
 * @type {object}
 * @property {number} x
 * @property {number} y
 * @property {number} width
 * @property {number} height
 */

/**
 * @typedef TRBL
 * @type {object}
 * @property {number} top
 * @property {number} right
 * @property {number} bottom
 * @property {number} left
 */

... and the following feature:

MyFeature.js

import 'typeDefs';

class Foo {

  /**
   * @param {Bounds}
   */
  foo(bounds) {
    // ...
  }
}

... the assistance in VSCode (IntelliSense) can properly provide full information about fooFeature#foo:

import FooFeature from 'FooFeature';

class BarFeature {
  
  /**
   * @param {FooFeature} fooFeature
   */
  constructor(fooFeature) {
    fooFeature.foo(); // IntelliSense to the rescue!
  }
}

A better way of importing things (seen in TypeScript, too) is by actually importing the used types from somewhere and use them in the documentation tags, only:

import { Bounds } from 'diagram-js-types';

class Foo {

  /**
   * @param {Bounds}
   */
  foo(bounds) {
    // ...
  }
}

Areas of Research

  • How to properly declare inherits relationships
  • How to properly declare types for utilities, i.e. Bounds
  • Does importing types for typing support only impact bundle sizes (initial research shows no, as these import declarations are being tree-shaken)

@nikku nikku changed the title Improve type hints to support advanced code navigation and assistance in IDEs (Intellij, VSCode) Improve type hints to support advanced code navigation and assistance in IDEs (Intellij, VSCode) and LSPs Nov 27, 2020
@nikku nikku added documentation Improvements or additions to documentation and removed pr welcome We rely on a community contribution to improve this. labels Nov 27, 2020
@nikku
Copy link
Member

nikku commented Jan 18, 2021

As indicated via #332 (comment) you can now generate TypeScript definitions from JSDoc types.

@philippfromme
Copy link
Contributor Author

Closed by https://github.com/bpmn-io/internal-docs/issues/688. bpmn-js@13 includes type declarations.

@bpmn-io-tasks bpmn-io-tasks bot removed the backlog Queued in backlog label Apr 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants