runem / lit-analyzer

Monorepository for tools that analyze lit-html templates
MIT License
319 stars 38 forks source link

Generic Component Declarations #149

Open a11delavar opened 3 years ago

a11delavar commented 3 years ago

I was creating some custom elements from a generic base class using LitElement and came to some odd problems where the analyzer didn't type-check generic-related properties.

A simplified version of what I was doing:

export class GenericElement<T> extends LitElement {
    @property() key!: keyof T
}

declare global {
    interface HTMLElementTagNameMap {
        'generic-specific': GenericElement<{ id: number, name: string }>
    }
}

First I tried to do it the vanilla way and type checking works fine:

const element = document.createElement('generic-specific')
element.key = 'what' // ✅ TS2322: Type '"what"' is not assignable to type '"id" | "name"'.

However, for some reason, it doesn't work in tagged html templates:

export const someTemplate = () => html`
        <!-- ❌ Not type-checked -->
    <generic-specific key='what??'></generic-specific>
`

I guess the analyzer doesn't analyze the declared generic part in the HTMLElementTagNameMap? because it thinks the key is of type keyof T instead of keyof { id: number, name: string }.

Would be glad to know what I'm missing or to know if there are work-arounds for now. Also, if these generic declarations cannot be supported due to some TypeScript API limitations, would it make sense to disable these checks for now?

mohe2015 commented 2 years ago

Anybody knows workarounds inside of html tagged templates that verify the type checks? The only one I could find is:

export function pwSelectInput<T, Q extends keyof T>(props: Pick<PwSelectInput<T, Q>, "onchange" | "disabled" | "initial" | "label" | "name" | "options" | "task">) {
  const {
    onchange,
    disabled,
    initial,
    label,
    name,
    options,
    task,
    ...rest
  } = props;
  const _: {} = rest;
  return html`<pw-select-input
    ?disabled=${disabled}
    @change=${onchange}
    label=${label}
    .name=${name}
    .options=${options}
    .task=${task}
    .initial=${initial}
  ></pw-select-input>`;
}

which I think is super ugly but it seems to work.

And how realistic is it that this gets implemented natively?