Lit Analyzer
Lit Analyzer is a command-line interface (CLI) tool designed to provide static analysis and type checking for Lit templates within JavaScript and TypeScript projects. It enhances the developer experience by catching common errors related to Lit element properties, events, and slots before runtime. The current stable version is 2.0.3, with regular updates to support new Lit features and address community feedback. Key differentiators include its deep integration with TypeScript for template-bound expressions, offering autocompletion suggestions, and enforcing best practices through configurable rules, such as ensuring proper property visibility (`@property`, `@internalProperty`) and correct custom element registration in `HTMLElementTagNameMap`. Its development is active, with significant improvements introduced in the transition to v2.
Common errors
-
Error: Could not find a tsconfig.json in the current working directory or any of its parent directories.
cause `lit-analyzer` requires a `tsconfig.json` for type checking, but none was found in the project's hierarchy.fixCreate a `tsconfig.json` file in your project root. A minimal configuration will suffice for `lit-analyzer` to operate, e.g., `{ "compilerOptions": { "target": "es2020", "module": "esnext", "moduleResolution": "node" }, "include": ["src/**/*.ts"] }`. -
Property 'someProperty' does not exist on type 'MyElement'. Did you mean 'someOtherProperty'?
cause A property or attribute used in a Lit template expression (e.g., `${this.someProperty}`) is not defined on the corresponding LitElement class, or its type definition is incorrect.fixDefine the property explicitly on your LitElement class, ensuring it has the correct name and type, and is potentially decorated with `@property()` if it's a reactive property. Double-check for typos. -
Custom element 'my-component' is not defined in 'HTMLElementTagNameMap'.
cause The custom element tag name is used in a template, but the element's type definition is not registered in the global `HTMLElementTagNameMap` interface, which `lit-analyzer` uses for type checking.fixAdd a `declare global` block in a TypeScript file to extend `HTMLElementTagNameMap` with your custom element's tag and class type: `declare global { interface HTMLElementTagNameMap { 'my-component': MyComponentClass; } }`. -
Event 'some-invalid-event' does not exist on element 'button'.
cause An event listener is specified in a template for an event that is not a standard DOM event or is not explicitly declared as a custom event by the element.fixEnsure that the event name is a valid DOM event (e.g., `click`, `input`) or a correctly dispatched custom event from the component. If it's a custom event, ensure the component itself dispatches it correctly and consider adding JSDoc `@fires` tags for better documentation and tooling.
Warnings
- breaking Upgrading to version 2.0.0 from a 1.x.x release may introduce stricter type checking and require adjustments to your Lit component definitions or template usage. While no major API surface changes were explicitly documented as 'breaking' for users, internal architectural shifts and tightened rule defaults are common in major version bumps for static analysis tools.
- gotcha For `lit-analyzer` to perform comprehensive analysis, especially type checking, a `tsconfig.json` file must be present and correctly configured in your project root or specified via the `--config` flag. Without it, the analyzer operates in a more limited mode.
- gotcha The `no-missing-element-type-definition` rule, introduced in v1.2.0, ensures that all custom elements are properly registered in the `HTMLElementTagNameMap` TypeScript interface. Failing to do so will result in a warning.
- gotcha The `no-property-visibility-mismatch` rule, added in v1.2.0, enforces the use of `@property` decorator for public properties and `@internalProperty` (or no decorator) for non-public ones. This helps maintain a clear API surface for your Lit components.
Install
-
npm install lit-analyzer -
yarn add lit-analyzer -
pnpm add lit-analyzer
Quickstart
{
"name": "lit-analyzer-example",
"version": "1.0.0",
"scripts": {
"analyze": "lit-analyzer --strict"
},
"devDependencies": {
"lit": "^2.0.0",
"lit-analyzer": "^2.0.0",
"typescript": "^4.0.0"
}
}
// my-element.ts
import { LitElement, html, property } from 'lit';
import { customElement } from 'lit/decorators.js';
interface MyData {
value: string;
}
@customElement('my-element')
export class MyElement extends LitElement {
@property({ type: String })
name: string = 'World';
@property({ type: Number })
count: number = 0;
@property({ attribute: false })
data?: MyData;
render() {
return html`
<h1>Hello, ${this.name}!</h1>
<p>Count: ${this.count}</p>
${this.data ? html`<p>Data value: ${this.data.value}</p>` : ''}
<button @click="${this._increment}">Increment</button>
<!-- Lit Analyzer will warn about invalid event name 'my-event' -->
<button @my-event="${() => console.log('This will error')}">Custom Event</button>
<!-- Lit Analyzer will warn about undefined property 'undefinedProp' -->
<p>${(this as any).undefinedProp}</p> <!-- Cast to any to prevent TS error in editor -->
`;
}
private _increment() {
this.count++;
}
}
declare global {
interface HTMLElementTagNameMap {
'my-element': MyElement;
}
}
// To run:
// 1. npm install
// 2. npm run analyze