webcomponents/custom-elements-manifest

TypeScript error: "tagName" does not exist in type 'ClassDeclaration'

nolanlawson opened this issue · 2 comments

I originally opened this issue on the custom-elements-manifest analyzer (open-wc/custom-elements-manifest#43), but I'm told this is a bug in the spec, so reopening here.

In the custom-elements-manifest analyzer playground, use this custom element:

class MyElement extends HTMLElement {}
customElements.define("my-element", MyElement)

You'll get this JSON:

click to expand
{
  "schemaVersion": "1.0.0",
  "readme": "",
  "modules": [
    {
      "kind": "javascript-module",
      "path": "src/my-element.js",
      "declarations": [
        {
          "kind": "class",
          "description": "",
          "name": "MyElement",
          "superclass": {
            "name": "HTMLElement"
          },
          "tagName": "my-element",
          "customElement": true
        }
      ],
      "exports": [
        {
          "kind": "custom-element-definition",
          "name": "my-element",
          "declaration": {
            "name": "MyElement",
            "module": "src/my-element.js"
          }
        }
      ]
    }
  ]
}

If you compare this to the schema, it doesn't match because of tagName on the ClassDeclaration.

You can repro using a simple TypeScript file:

click to expand
import { Package } from 'custom-elements-manifest/schema'

const json: Package = {
  "schemaVersion": "1.0.0",
  "readme": "",
  "modules": [
    {
      "kind": "javascript-module",
      "path": "index.js",
      "declarations": [
        {
          "kind": "class",
          "description": "",
          "name": "MyElement",
          "superclass": {
            "name": "HTMLElement"
          },
          "tagName": "my-element",
          "customElement": true
        }
      ],
      "exports": [
        {
          "kind": "custom-element-definition",
          "name": "my-element",
          "declaration": {
            "name": "MyElement",
            "module": "index.js"
          }
        }
      ]
    }
  ]
}

Then run:

mkdir test
cd test
npm init --yes
npm i --save custom-elements-manifest@1.0.0 typescript@4.3.4
npx tsc index.ts

You'll see the error:

test.ts:18:11 - error TS2322: Type '{ kind: "class"; description: string; name: string; superclass: { name: string; }; tagName: string; customElement: true; }' is not assignable to type 'Declaration'.
  Object literal may only specify known properties, and '"tagName"' does not exist in type 'ClassDeclaration'.

18           "tagName": "my-element",
             ~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error.

Expected behavior

I would expect the output schema to match the one from schema.d.ts.

Same problem I ran into when updating the analyzer's implementation of this.

It's because the custom element declaration interface is empty, while there's another unused interface containing what we want:

export interface CustomElementDeclaration extends ClassDeclaration {}

There's an interface below that which feels like it was meant to be the custom element declaration but somehow we ended up with two

The JSON above is correct, at least according to our intention on how to identify a class as a custom element. CustomElementDeclaration also needs to extend CustomElement though.

As for the error, we have one interface that extends another. Additional fields in the sub-interface should be legal. TypeScript has a check that unknown properties aren't allowed, but for some reason that isn't triggered when I tried to repro in Stackblitz: https://stackblitz.com/edit/typescript-plhrbv?file=index.ts

I'd like to get a repro so that I can add a "test" (just an object literal like above) to ensure that CustomElementDeclaration extending CustomElement fixes it. I'll work up a PR with the change right away though.