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

custom-element-jet-brains-integration: Various problems with generating JS properties and events for Web Types #96

Closed
nolan-white opened this issue Mar 6, 2024 · 1 comment

Comments

@nolan-white
Copy link
Contributor

nolan-white commented Mar 6, 2024

Issue

There are various problems with generating JS properties and events for Web Types. For properties, the custom elements attributes will be used to define the properties if they are present even though attributes are separate from properties and they don't correspond to each other. If attributes are not present and the actual properties are used, all properties, including static and private properties, will be generated in the Web Types.

JetBrains IDEs use these property definitions to provide autocomplete and type information for public non-static HTML element properties when using frameworks like Angular, so the inclusion of static or private properties will be misleading since there is no way to denote this via the Web Types schema.

As for generating JS events for Web Types, the tool currently does not transfer event types from the CEM, and it will also include events without names in the generated Web Types file, which the CEM analyzer sometimes generates.

Example

Here is an example of the issues described. The following code snippet shows the custom elements manifest to convert into Web Types.

{
  "schemaVersion": "1.0.0",
  "readme": "",
  "modules": [
    {
      "kind": "javascript-module",
      "path": "src/my-custom-element.ts",
      "declarations": [
        {
          "kind": "class",
          "description": "An element that is my own.",
          "name": "MyCustomElement",
          "members": [
            {
              "kind": "field",
              "name": "TAG",
              "type": {
                "text": "string"
              },
              "static": true,
              "readonly": true,
              "default": "'my-custom-element'"
            },
            {
              "kind": "field",
              "name": "myProperty",
              "type": {
                "text": "number"
              },
              "default": "1",
              "description": "A property that is my own"
            },
            {
              "kind": "field",
              "name": "myPrivateProperty",
              "type": {
                "text": "boolean"
              },
              "privacy": "private",
              "default": "true"
            }
          ],
          "events": [
            {
              "type": {
                "text": "MyCustomEvent"
              }
            },
            {
              "type": {
                "text": "MyCustomEvent"
              },
              "description": "An event that is my own",
              "name": "mycustomevent"
            }
          ],
          "attributes": [
            {
              "name": "color"
            }
          ],
          "superclass": {
            "name": "HTMLElement"
          },
          "tagName": "my-custom-element",
          "customElement": true
        }
      ],
      "exports": [
        {
          "kind": "custom-element-definition",
          "declaration": {
            "name": "MyCustomElement",
            "module": "src/my-custom-element.ts"
          }
        }
      ]
    }
  ]
}

This CEM was generated by the CEM analyzer using the following TypeScript file.

// my-custom-element.ts

interface MyCustomEventDetail {
  foo: string;
}
interface MyCustomEventInit extends CustomEventInit<MyCustomEventDetail> {}

class MyCustomEvent extends CustomEvent<MyCustomEventDetail> {
  static readonly TYPE = 'mycustomevent'

  constructor(init: MyCustomEventInit) {
    super(MyCustomEvent.TYPE, init);
  }
}

/**
 * An element that is my own.
 *
 * @property myProperty A property that is my own
 *
 * @fires {MyCustomEvent} mycustomevent An event that is my own
 *
 * @tag my-custom-element
 */
class MyCustomElement extends HTMLElement {
  static readonly observedAttributes: ReadonlyArray<string> = [
      'color'
  ]

  static readonly TAG = 'my-custom-element'

  myProperty: number = 1;
  private myPrivateProperty: boolean = true;

  connectedCallback(): void {
    this.dispatchEvent(new MyCustomEvent({
      detail: {
        foo: 'bar',
      }
    }))
    console.log("Custom element added to page.");
  }

  disconnectedCallback(): void {
    console.log("Custom element removed from page.");
  }

  adoptedCallback(): void {
    console.log("Custom element moved to new page.");
  }

  attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
    console.log(`Attribute "${name}" changed from "${oldValue}" to "${newValue}`);
  }
}

customElements.define(MyCustomElement.TAG, MyCustomElement)

I expect the generated Web Types file to look like this:

{
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
  "name": "cem-to-webtypes-testing",
  "version": "1.0.0",
  "description-markup": "markdown",
  "contributions": {
    "html": {
      "elements": [
        {
          "name": "my-custom-element",
          "description": "An element that is my own.\n---\n\n\n### **Events:**\n - **mycustomevent** - An event that is my own",
          "doc-url": "",
          "attributes": [{ "name": "color", "value": { "type": "string" } }],
          "js": {
            "properties": [
              {
                "name": "myProperty",
                "description": "A property that is my own",
                "type": "number"
              }
            ],
            "events": [
              {
                "name": "mycustomevent",
                "type": "MyCustomEvent",
                "description": "An event that is my own"
              }
            ]
          }
        }
      ]
    },
    "css": {
      "properties": [],
      "pseudo-elements": []
    }
  }
}

But it actually looks like this:

{
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
  "name": "cem-to-webtypes-testing",
  "version": "1.0.0",
  "description-markup": "markdown",
  "contributions": {
    "html": {
      "elements": [
        {
          "name": "my-custom-element",
          "description": "An element that is my own.\n---\n\n\n### **Events:**\n - **undefined** - undefined\n- **mycustomevent** - An event that is my own",
          "doc-url": "",
          "attributes": [{ "name": "color", "value": { "type": "string" } }],
          "events": [
            {},
            {
              "name": "mycustomevent",
              "description": "An event that is my own"
            }
          ],
          "js": {
            "properties": [{ "name": "color", "value": {} }],
            "events": [
              {},
              {
                "name": "mycustomevent",
                "description": "An event that is my own"
              }
            ]
          }
        }
      ]
    },
    "css": {
      "properties": [],
      "pseudo-elements": []
    }
  }
}

To give an example of how this causes a problem in the IDE, when I go to use the custom element in Angular with the generated Web Types, I can see there is both an attribute and property called color, and no property called myProperty.

image

@nolan-white
Copy link
Contributor Author

Fixed by #97, closing.

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

No branches or pull requests

1 participant