diff --git a/dev/src/my-element-1.ts b/dev/src/my-element-1.ts
index 9afe595a..574caae9 100644
--- a/dev/src/my-element-1.ts
+++ b/dev/src/my-element-1.ts
@@ -13,8 +13,11 @@ export class MyElement extends LitElement {
return ["this is a test", "testing"];
}
+ didClick(evt: MouseEvent) {}
+
render() {
return html`
+
{}}" globalattribute>
diff --git a/docs/readme/rules.md b/docs/readme/rules.md
index 2c1cd08c..a64e3caf 100644
--- a/docs/readme/rules.md
+++ b/docs/readme/rules.md
@@ -31,7 +31,6 @@ Each rule can have severity of `off`, `warning` or `error`. You can toggle rules
| :------ | ----------- | --------------- | --------------- |
| [no-invalid-boolean-binding](#-no-invalid-boolean-binding) | Disallow boolean attribute bindings on non-boolean types. | error | error |
| [no-expressionless-property-binding](#-no-expressionless-property-binding) | Disallow property bindings without an expression. | error | error |
-| [no-noncallable-event-binding](#-no-noncallable-event-binding) | Disallow event listener bindings with a noncallable type. | error | error |
| [no-boolean-in-attribute-binding](#-no-boolean-in-attribute-binding) | Disallow attribute bindings with a boolean type. | error | error |
| [no-complex-attribute-binding](#-no-complex-attribute-binding) | Disallow attribute bindings with a complex type. | error | error |
| [no-nullable-attribute-binding](#-no-nullable-attribute-binding) | Disallow attribute bindings with nullable types such as "null" or "undefined". | error | error |
@@ -296,26 +295,6 @@ The following example is not considered a warning:
html``
```
-#### 🌀 no-noncallable-event-binding
-
-It's a common mistake to incorrectly call the function when setting up an event handler binding instead of passing a reference to the function. This makes the function call whenever the code evaluates.
-
-The following examples are considered warnings:
-
-
-```js
-html``
-html``
-```
-
-The following examples are not considered warnings:
-
-
-```js
-html``
-html``
-```
-
#### 😈 no-boolean-in-attribute-binding
You should not be binding to a boolean type using an attribute binding because it could result in binding the string "true" or "false". Instead you should be using a **boolean** attribute binding.
@@ -386,6 +365,8 @@ html``
html``
html``
html``
+html``
+html``
```
The following examples are not considered warnings:
@@ -396,6 +377,8 @@ html``
html``
html``
html``
+html``
+html``
```
#### 💥 no-invalid-directive-binding
diff --git a/packages/lit-analyzer/README.md b/packages/lit-analyzer/README.md
index b4f436fb..bd0e2ff6 100644
--- a/packages/lit-analyzer/README.md
+++ b/packages/lit-analyzer/README.md
@@ -104,7 +104,6 @@ Each rule can have severity of `off`, `warning` or `error`. You can toggle rules
| :------ | ----------- | --------------- | --------------- |
| [no-invalid-boolean-binding](#-no-invalid-boolean-binding) | Disallow boolean attribute bindings on non-boolean types. | error | error |
| [no-expressionless-property-binding](#-no-expressionless-property-binding) | Disallow property bindings without an expression. | error | error |
-| [no-noncallable-event-binding](#-no-noncallable-event-binding) | Disallow event listener bindings with a noncallable type. | error | error |
| [no-boolean-in-attribute-binding](#-no-boolean-in-attribute-binding) | Disallow attribute bindings with a boolean type. | error | error |
| [no-complex-attribute-binding](#-no-complex-attribute-binding) | Disallow attribute bindings with a complex type. | error | error |
| [no-nullable-attribute-binding](#-no-nullable-attribute-binding) | Disallow attribute bindings with nullable types such as "null" or "undefined". | error | error |
@@ -369,26 +368,6 @@ The following example is not considered a warning:
html``
```
-#### 🌀 no-noncallable-event-binding
-
-It's a common mistake to incorrectly call the function when setting up an event handler binding instead of passing a reference to the function. This makes the function call whenever the code evaluates.
-
-The following examples are considered warnings:
-
-
-```js
-html``
-html``
-```
-
-The following examples are not considered warnings:
-
-
-```js
-html``
-html``
-```
-
#### 😈 no-boolean-in-attribute-binding
You should not be binding to a boolean type using an attribute binding because it could result in binding the string "true" or "false". Instead you should be using a **boolean** attribute binding.
@@ -459,6 +438,8 @@ html``
html``
html``
html``
+html``
+html``
```
The following examples are not considered warnings:
@@ -469,6 +450,8 @@ html``
html``
html``
html``
+html``
+html``
```
#### 💥 no-invalid-directive-binding
diff --git a/packages/lit-analyzer/src/analyze/default-lit-analyzer-context.ts b/packages/lit-analyzer/src/analyze/default-lit-analyzer-context.ts
index 3ec8a1c8..2a4798e2 100644
--- a/packages/lit-analyzer/src/analyze/default-lit-analyzer-context.ts
+++ b/packages/lit-analyzer/src/analyze/default-lit-analyzer-context.ts
@@ -227,7 +227,7 @@ export class DefaultLitAnalyzerContext implements LitAnalyzerContext {
ts: this.ts,
config: {
features: ["event", "member", "slot", "csspart", "cssproperty"],
- analyzeGlobalFeatures: !isDefaultLibrary, // Don't analyze global features in lib.dom.d.ts
+ analyzeGlobalFeatures: true,
analyzeDefaultLib: true,
analyzeDependencies: true,
analyzeAllDeclarations: false,
@@ -235,6 +235,13 @@ export class DefaultLitAnalyzerContext implements LitAnalyzerContext {
}
});
+ // Don't analyze global members in lib.dom.d.ts for now
+ // We already merge in content from "HTMLElement", but in the future we might
+ // only use "globalFeatures" instead
+ if (isDefaultLibrary && analyzeResult.globalFeatures != null) {
+ analyzeResult.globalFeatures.members = [];
+ }
+
const reg = isDefaultLibrary ? HtmlDataSourceKind.BUILT_IN_DECLARED : HtmlDataSourceKind.DECLARED;
// Forget
diff --git a/packages/lit-analyzer/src/analyze/lit-analyzer-config.ts b/packages/lit-analyzer/src/analyze/lit-analyzer-config.ts
index 5c590fd3..c24acfb8 100644
--- a/packages/lit-analyzer/src/analyze/lit-analyzer-config.ts
+++ b/packages/lit-analyzer/src/analyze/lit-analyzer-config.ts
@@ -14,7 +14,6 @@ export type LitAnalyzerRuleId =
| "no-unintended-mixed-binding"
| "no-invalid-boolean-binding"
| "no-expressionless-property-binding"
- | "no-noncallable-event-binding"
| "no-boolean-in-attribute-binding"
| "no-complex-attribute-binding"
| "no-nullable-attribute-binding"
@@ -45,7 +44,6 @@ const DEFAULT_RULES_SEVERITY: Record): LitA
if (getDeprecatedOption(userOptions, "skipTypeChecking") === true) {
Object.assign(mappedDeprecatedRules, {
"no-invalid-boolean-binding": "off",
- "no-noncallable-event-binding": "off",
"no-boolean-in-attribute-binding": "off",
"no-complex-attribute-binding": "off",
"no-nullable-attribute-binding": "off",
diff --git a/packages/lit-analyzer/src/analyze/parse/parse-html-data/html-tag.ts b/packages/lit-analyzer/src/analyze/parse/parse-html-data/html-tag.ts
index 8bbf4fe8..3726410a 100644
--- a/packages/lit-analyzer/src/analyze/parse/parse-html-data/html-tag.ts
+++ b/packages/lit-analyzer/src/analyze/parse/parse-html-data/html-tag.ts
@@ -2,6 +2,7 @@ import { isAssignableToSimpleTypeKind, SimpleType, typeToString } from "ts-simpl
import { ComponentCssPart, ComponentCssProperty, ComponentDeclaration, ComponentEvent, ComponentMember, ComponentSlot } from "web-component-analyzer";
import { LIT_HTML_BOOLEAN_ATTRIBUTE_MODIFIER, LIT_HTML_EVENT_LISTENER_ATTRIBUTE_MODIFIER, LIT_HTML_PROP_ATTRIBUTE_MODIFIER } from "../../constants";
import { iterableDefined } from "../../util/iterable-util";
+import { lazy } from "../../util/general-util";
export interface HtmlDataFeatures {
attributes: HtmlAttr[];
@@ -188,7 +189,10 @@ export function documentationForHtmlTag(htmlTag: HtmlTag, options: DescriptionOp
desc += `\n\n${descriptionList(
"Events",
items,
- event => `${descriptionHeader(`@fires ${event.name}`, 0, options)}${event.description ? ` - ${event.description}` : ""}`,
+ event =>
+ `${descriptionHeader(`@fires ${typeToString(event.getType())} ${event.name}`, 0, options)}${
+ event.description ? ` - ${event.description}` : ""
+ }`,
options
)}`;
}
@@ -256,7 +260,31 @@ export function mergeHtmlProps(props: HtmlProp[]): HtmlProp[] {
}
export function mergeHtmlEvents(events: HtmlEvent[]): HtmlEvent[] {
- return mergeFirstUnique(events, event => event.name);
+ if (events.length <= 1) {
+ return events;
+ }
+
+ const mergedEvents = new Map();
+ for (const evt of events) {
+ const existingEvent = mergedEvents.get(evt.name);
+ if (existingEvent != null) {
+ mergedEvents.set(evt.name, {
+ ...evt,
+ declaration: existingEvent.declaration || evt.declaration,
+ fromTagName: existingEvent.fromTagName || evt.fromTagName,
+ builtIn: existingEvent.builtIn || evt.builtIn,
+ global: existingEvent.global || evt.global,
+ description: existingEvent.description || evt.description,
+ getType: lazy(() => {
+ const type = existingEvent.getType();
+ return type.kind === "ANY" ? evt.getType() : type;
+ })
+ });
+ } else {
+ mergedEvents.set(evt.name, evt);
+ }
+ }
+ return Array.from(mergedEvents.values());
}
export function mergeHtmlSlots(slots: HtmlSlot[]): HtmlSlot[] {
diff --git a/packages/lit-analyzer/src/analyze/store/html-store/html-data-source-merged.ts b/packages/lit-analyzer/src/analyze/store/html-store/html-data-source-merged.ts
index ecf46b9e..3c6d3173 100644
--- a/packages/lit-analyzer/src/analyze/store/html-store/html-data-source-merged.ts
+++ b/packages/lit-analyzer/src/analyze/store/html-store/html-data-source-merged.ts
@@ -458,24 +458,34 @@ function mergeRelatedMembers(members: Iterable): Readon
return mergedMembers;
}
-function mergeRelatedTypeToUnion(typeA: SimpleType, typeB: SimpleType): SimpleType {
+function mergeRelatedTypeToUnion(typeA: SimpleType, typeB: SimpleType, { collapseAny = true }: { collapseAny?: boolean } = {}): SimpleType {
if (typeA.kind === typeB.kind) {
- switch (typeA.kind) {
- case "ANY":
- return typeA;
+ return typeA;
+ }
+
+ if (collapseAny) {
+ if (typeB.kind === "ANY") {
+ return typeA;
+ } else if (typeA.kind === "ANY") {
+ return typeB;
}
}
- switch (typeA.kind) {
- case "UNION":
- if (typeB.kind === "ANY" && typeA.types.find(t => t.kind === "ANY") != null) {
+ if (typeA.kind === "UNION" && typeB.kind === "UNION") {
+ if (collapseAny) {
+ if (typeA.types.some(t => t.kind === "ANY")) {
+ return typeB;
+ }
+
+ if (typeB.types.some(t => t.kind === "ANY")) {
return typeA;
- } else {
- return {
- ...typeA,
- types: [...typeA.types, typeB]
- };
}
+ }
+
+ return {
+ ...typeA,
+ types: Array.from(new Set([...typeA.types, ...typeB.types]))
+ };
}
return {
@@ -533,7 +543,7 @@ function mergeRelatedEvents(events: Iterable): ReadonlyMap mergeRelatedTypeToUnion(prevType(), event.getType())),
+ getType: lazy(() => mergeRelatedTypeToUnion(prevType(), event.getType(), { collapseAny: false })),
related: existingEvent.related == null ? [existingEvent, event] : [...existingEvent.related, event],
fromTagName: existingEvent.fromTagName || event.fromTagName
});
diff --git a/packages/lit-analyzer/src/rules/all-rules.ts b/packages/lit-analyzer/src/rules/all-rules.ts
index 3e0b567f..66e77ab7 100644
--- a/packages/lit-analyzer/src/rules/all-rules.ts
+++ b/packages/lit-analyzer/src/rules/all-rules.ts
@@ -10,7 +10,6 @@ import noInvalidTagName from "./no-invalid-tag-name";
import noLegacyAttribute from "./no-legacy-attribute";
import noMissingElementTypeDefinition from "./no-missing-element-type-definition";
import noMissingImport from "./no-missing-import";
-import noNoncallableEventBindingRule from "./no-noncallable-event-binding";
import noNullableAttributeBindingRule from "./no-nullable-attribute-binding";
import noPropertyVisibilityMismatch from "./no-property-visibility-mismatch";
import noUnclosedTag from "./no-unclosed-tag";
@@ -25,7 +24,6 @@ export const ALL_RULES: RuleModule[] = [
noExpressionlessPropertyBindingRule,
noUnintendedMixedBindingRule,
noUnknownSlotRule,
- noNoncallableEventBindingRule,
noNullableAttributeBindingRule,
noComplexAttributeBindingRule,
noBooleanInAttributeBindingRule,
diff --git a/packages/lit-analyzer/src/rules/no-incompatible-type-binding.ts b/packages/lit-analyzer/src/rules/no-incompatible-type-binding.ts
index 016b0605..d4fb2e7e 100644
--- a/packages/lit-analyzer/src/rules/no-incompatible-type-binding.ts
+++ b/packages/lit-analyzer/src/rules/no-incompatible-type-binding.ts
@@ -8,6 +8,7 @@ import { extractBindingTypes } from "./util/type/extract-binding-types";
import { isAssignableInAttributeBinding } from "./util/type/is-assignable-in-attribute-binding";
import { isAssignableInBooleanBinding } from "./util/type/is-assignable-in-boolean-binding";
import { isAssignableInPropertyBinding } from "./util/type/is-assignable-in-property-binding";
+import { isAssignableInEventBinding } from "./util/type/is-assignable-in-event-binding";
/**
* This rule validate if the types of a binding are assignable.
@@ -37,6 +38,7 @@ const rule: RuleModule = {
break;
case LIT_HTML_EVENT_LISTENER_ATTRIBUTE_MODIFIER:
+ isAssignableInEventBinding(htmlAttr, { typeA, typeB }, context);
break;
default: {
diff --git a/packages/lit-analyzer/src/rules/no-noncallable-event-binding.ts b/packages/lit-analyzer/src/rules/no-noncallable-event-binding.ts
deleted file mode 100644
index 6e8cb0f4..00000000
--- a/packages/lit-analyzer/src/rules/no-noncallable-event-binding.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { isAssignableToSimpleTypeKind, SimpleType, typeToString, validateType } from "ts-simple-type";
-import { HtmlNodeAttrKind } from "../analyze/types/html-node/html-node-attr-types";
-import { RuleModule } from "../analyze/types/rule/rule-module";
-import { rangeFromHtmlNodeAttr } from "../analyze/util/range-util";
-import { extractBindingTypes } from "./util/type/extract-binding-types";
-
-/**
- * This rule validates that only callable types are used within event binding expressions.
- * This rule catches typos like: @click="onClick()"
- */
-const rule: RuleModule = {
- id: "no-noncallable-event-binding",
- meta: {
- priority: "high"
- },
- visitHtmlAssignment(assignment, context) {
- // Only validate event listener bindings.
- const { htmlAttr } = assignment;
- if (htmlAttr.kind !== HtmlNodeAttrKind.EVENT_LISTENER) return;
-
- const { typeB } = extractBindingTypes(assignment, context);
-
- // Make sure that the expression given to the event listener binding a function or an object with "handleEvent" property.
- if (!isTypeBindableToEventListener(typeB)) {
- context.report({
- location: rangeFromHtmlNodeAttr(htmlAttr),
- message: `You are setting up an event listener with a non-callable type '${typeToString(typeB)}'`
- });
- }
- }
-};
-
-export default rule;
-
-/**
- * Returns if this type can be used in a event listener binding
- * @param type
- */
-function isTypeBindableToEventListener(type: SimpleType): boolean {
- // Return "true" if the type has a call signature
- if ("call" in type && type.call != null) {
- return true;
- }
-
- // Callable types can be used in the binding
- if (isAssignableToSimpleTypeKind(type, ["FUNCTION", "METHOD", "UNKNOWN"], { matchAny: true })) {
- return true;
- }
-
- return validateType(type, simpleType => {
- switch (simpleType.kind) {
- // Object types with attributes for the setup function of the event listener can be used
- case "OBJECT":
- case "INTERFACE": {
- // The "handleEvent" property must be present
- const handleEventFunction = simpleType.members != null ? simpleType.members.find(m => m.name === "handleEvent") : undefined;
-
- // The "handleEvent" property must be callable
- if (handleEventFunction != null) {
- return isTypeBindableToEventListener(handleEventFunction.type);
- }
- }
- }
-
- return undefined;
- });
-}
diff --git a/packages/lit-analyzer/src/rules/util/type/is-assignable-in-event-binding.ts b/packages/lit-analyzer/src/rules/util/type/is-assignable-in-event-binding.ts
new file mode 100644
index 00000000..8fe69092
--- /dev/null
+++ b/packages/lit-analyzer/src/rules/util/type/is-assignable-in-event-binding.ts
@@ -0,0 +1,129 @@
+import { isAssignableToSimpleTypeKind, SimpleType, typeToString, validateType } from "ts-simple-type";
+import { parseSimpleJsDocTypeExpression } from "web-component-analyzer";
+import { HtmlNodeAttr } from "../../../analyze/types/html-node/html-node-attr-types";
+import { RuleModuleContext } from "../../../analyze/types/rule/rule-module-context";
+import { rangeFromHtmlNodeAttr } from "../../../analyze/util/range-util";
+import { isAssignableToType } from "./is-assignable-to-type";
+
+export function isAssignableInEventBinding(
+ htmlAttr: HtmlNodeAttr,
+ { typeA, typeB }: { typeA: SimpleType; typeB: SimpleType },
+ context: RuleModuleContext
+): boolean | undefined {
+ // Make sure that the expression given to the event listener binding a function or an object with "handleEvent" property.
+ // This catches typos like: @click="onClick()"
+ if (!isTypeBindableToEventListener(typeB)) {
+ context.report({
+ location: rangeFromHtmlNodeAttr(htmlAttr),
+ message: `You are setting up an event listener with a non-callable type '${typeToString(typeB)}'`
+ });
+
+ return false;
+ }
+
+ return isEventAssignable(htmlAttr, { typeA, typeB }, context);
+}
+
+export function isEventAssignable(
+ htmlAttr: HtmlNodeAttr,
+ { typeA, typeB }: { typeA: SimpleType; typeB: SimpleType },
+ context: RuleModuleContext
+): boolean | undefined {
+ // Treat type "ANY" as "Event"
+ if (typeA.kind === "ANY") {
+ typeA = parseSimpleJsDocTypeExpression("Event", context);
+ }
+
+ // Construct an event handler type to perform type checking against
+ const expectedType: SimpleType = {
+ kind: "OBJECT",
+ call: {
+ kind: "FUNCTION",
+ returnType: { kind: "VOID" },
+ parameters: [
+ {
+ name: "event",
+ type: typeA,
+ optional: false,
+ rest: false,
+ initializer: false
+ }
+ ]
+ }
+ };
+
+ // Extract the type of "handleEvent" from typeB if an object was given to the event binding
+ switch (typeB.kind) {
+ case "OBJECT":
+ case "INTERFACE": {
+ const handleEventMember = typeB.members != null ? typeB.members.find(m => m.name === "handleEvent") : undefined;
+ if (handleEventMember != null) {
+ typeB = handleEventMember.type;
+ }
+ break;
+ }
+ }
+
+ let assignable: boolean;
+ if (typeA.kind === "UNION") {
+ // If events have been merged into one UNION type, check each event type separately
+ const mutedContext = { ...context, report() {} };
+ assignable = typeA.types.some(tA => isAssignableInEventBinding(htmlAttr, { typeA: tA, typeB }, mutedContext));
+ } else {
+ // We don't always know the exact type of event required. Therefore always type check
+ // the parameters bivariant instead of contravariant. We primarily care if the parameter in
+ // typeB is an Event (or something more specific), and this is achieved forcing "strictFunctionTypes"
+ // to false (to have bivariant type checking for parameters)
+ // Examples on invalid error message we get if "strictFunctionTypes" is true:
+ // Type '(event: MouseEvent) => void' is not assignable to '(event: Event) => void'
+ // Type '(event: CustomEvent) => void' is not assignable to '(event: Event) => void'
+ // In theory, we would be able to set "strictFunctionTypes: true" if we knew the exact type required,
+ // but under many circumstances we don't know exactly
+ const strictFunctionTypes = false;
+
+ assignable = isAssignableToType({ typeA: expectedType, typeB }, context, { strictFunctionTypes });
+ }
+
+ if (!assignable) {
+ context.report({
+ location: rangeFromHtmlNodeAttr(htmlAttr),
+ message: `Type '${typeToString(typeB)}' is not assignable to '${typeToString(expectedType)}'`
+ });
+ }
+
+ return assignable;
+}
+
+/**
+ * Returns if this type can be used in a event listener binding
+ * @param type
+ */
+function isTypeBindableToEventListener(type: SimpleType): boolean {
+ // Return "true" if the type has a call signature
+ if ("call" in type && type.call != null) {
+ return true;
+ }
+
+ // Callable types can be used in the binding
+ if (isAssignableToSimpleTypeKind(type, ["FUNCTION", "METHOD", "UNKNOWN"], { matchAny: true })) {
+ return true;
+ }
+
+ return validateType(type, simpleType => {
+ switch (simpleType.kind) {
+ // Object types with attributes for the setup function of the event listener can be used
+ case "OBJECT":
+ case "INTERFACE": {
+ // The "handleEvent" property must be present
+ const handleEventMember = simpleType.members != null ? simpleType.members.find(m => m.name === "handleEvent") : undefined;
+
+ // The "handleEvent" property must be callable
+ if (handleEventMember != null) {
+ return isTypeBindableToEventListener(handleEventMember.type);
+ }
+ }
+ }
+
+ return undefined;
+ });
+}
diff --git a/packages/lit-analyzer/test/helpers/generate-test-file.ts b/packages/lit-analyzer/test/helpers/generate-test-file.ts
index c7d6b951..f2b9794a 100644
--- a/packages/lit-analyzer/test/helpers/generate-test-file.ts
+++ b/packages/lit-analyzer/test/helpers/generate-test-file.ts
@@ -1,11 +1,12 @@
import { TestFile } from "./compile-files";
-export function makeElement({ properties, slots }: { properties?: string[]; slots?: string[] }): TestFile {
+export function makeElement({ properties, slots, events }: { properties?: string[]; slots?: string[]; events?: string[] }): TestFile {
return {
fileName: "my-element.ts",
text: `
/**
${(slots || []).map(slot => ` * @slot ${slot}`)}
+${(events || []).map(event => ` * @fires ${event}`)}
*/
class MyElement extends HTMLElement {
${(properties || []).map(prop => `@property() ${prop}`).join("\n")}
diff --git a/packages/lit-analyzer/test/rules/no-incompatible-type-binding.ts b/packages/lit-analyzer/test/rules/no-incompatible-type-binding.ts
index 29ecfa55..8791fcdb 100644
--- a/packages/lit-analyzer/test/rules/no-incompatible-type-binding.ts
+++ b/packages/lit-analyzer/test/rules/no-incompatible-type-binding.ts
@@ -224,3 +224,120 @@ html\` \`
hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
});
+
+tsTest("Event binding: event handler is assignable to valid event", t => {
+ const { diagnostics } = getDiagnostics([makeElement({ events: ["foo-event"] }), "html` {}}>`"]);
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: event handler is assignable to valid typed event", t => {
+ const { diagnostics } = getDiagnostics([
+ makeElement({ events: ["{MouseEvent} foo-event"] }),
+ "html` {}}>`"
+ ]);
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: invalid event handler is not assignable to typed event", t => {
+ const { diagnostics } = getDiagnostics([
+ makeElement({ events: ["{MouseEvent} foo-event"] }),
+ "html` {}}>`"
+ ]);
+ hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
+});
+
+tsTest("Event binding: invalid event handler is not assignable to event", t => {
+ const { diagnostics } = getDiagnostics([
+ makeElement({ events: ["foo-event"] }),
+ "html` {}}>`"
+ ]);
+ hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
+});
+
+tsTest("Event binding: invalid event handler (generic custom event) is not assignable to typed event", t => {
+ const { diagnostics } = getDiagnostics([
+ makeElement({ events: ["{CustomEvent} foo-event"] }),
+ "html`) => {}}>`"
+ ]);
+ hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
+});
+
+tsTest("Event binding: event handler (generic custom event) is assignable to typed event", t => {
+ const { diagnostics } = getDiagnostics([
+ makeElement({ events: ["{CustomEvent} foo-event"] }),
+ "html`) => {}}>`"
+ ]);
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: event handler is assignable to event with unknown type", t => {
+ const { diagnostics } = getDiagnostics([
+ makeElement({ events: ["foo-event"] }),
+ "html` {}}>`"
+ ]);
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: event handler is assignable to merged event", t => {
+ const { diagnostics } = getDiagnostics([
+ makeElement({ events: ["{MouseEvent} foo-event"] }),
+ makeElement({ events: ["{KeyboardEvent} foo-event"] }),
+ "html` {}}>`"
+ ]);
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: Callable value is bindable", t => {
+ const { diagnostics } = getDiagnostics('html` {}}" />`');
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: Non callback value is not bindable", t => {
+ const { diagnostics } = getDiagnostics('html` {})()}" />`');
+ hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
+});
+
+tsTest("Event binding: Number is not bindable", t => {
+ const { diagnostics } = getDiagnostics('html``');
+ hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
+});
+
+tsTest("Event binding: Function is bindable", t => {
+ const { diagnostics } = getDiagnostics('function foo() {}; html``');
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: Called function is not bindable", t => {
+ const { diagnostics } = getDiagnostics('function foo() {}; html``');
+ hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
+});
+
+tsTest("Event binding: Any type is bindable", t => {
+ const { diagnostics } = getDiagnostics('html``');
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: Object with callable 'handleEvent' is bindable 1", t => {
+ const { diagnostics } = getDiagnostics('html` {}}}" />`');
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: Object with callable 'handleEvent' is bindable 2", t => {
+ const { diagnostics } = getDiagnostics('function foo() {}; html``');
+ hasNoDiagnostics(t, diagnostics);
+});
+
+tsTest("Event binding: Object with called 'handleEvent' is not bindable", t => {
+ const { diagnostics } = getDiagnostics('function foo() {}; html``');
+ hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
+});
+
+tsTest("Event binding: Object literal without 'handleEvent' is not bindable", t => {
+ const { diagnostics } = getDiagnostics('function foo() {}; html``');
+ hasDiagnostic(t, diagnostics, "no-incompatible-type-binding");
+});
+
+tsTest("Event binding: Mixed value binding with first expression being callable is bindable", t => {
+ const { diagnostics } = getDiagnostics('html``');
+ hasNoDiagnostics(t, diagnostics);
+});
diff --git a/packages/lit-analyzer/test/rules/no-noncallable-event-binding.ts b/packages/lit-analyzer/test/rules/no-noncallable-event-binding.ts
deleted file mode 100644
index 79744cee..00000000
--- a/packages/lit-analyzer/test/rules/no-noncallable-event-binding.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { getDiagnostics } from "../helpers/analyze";
-import { hasDiagnostic, hasNoDiagnostics } from "../helpers/assert";
-import { tsTest } from "../helpers/ts-test";
-
-tsTest("Event binding: Callable value is bindable", t => {
- const { diagnostics } = getDiagnostics('html` {}}" />`');
- hasNoDiagnostics(t, diagnostics);
-});
-
-tsTest("Event binding: Non callback value is not bindable", t => {
- const { diagnostics } = getDiagnostics('html` {})()}" />`');
- hasDiagnostic(t, diagnostics, "no-noncallable-event-binding");
-});
-
-tsTest("Event binding: Number is not bindable", t => {
- const { diagnostics } = getDiagnostics('html``');
- hasDiagnostic(t, diagnostics, "no-noncallable-event-binding");
-});
-
-tsTest("Event binding: Function is bindable", t => {
- const { diagnostics } = getDiagnostics('function foo() {}; html``');
- hasNoDiagnostics(t, diagnostics);
-});
-
-tsTest("Event binding: Called function is not bindable", t => {
- const { diagnostics } = getDiagnostics('function foo() {}; html``');
- hasDiagnostic(t, diagnostics, "no-noncallable-event-binding");
-});
-
-tsTest("Event binding: Any type is bindable", t => {
- const { diagnostics } = getDiagnostics('html``');
- hasNoDiagnostics(t, diagnostics);
-});
-
-tsTest("Event binding: Object with callable 'handleEvent' is bindable 1", t => {
- const { diagnostics } = getDiagnostics('html` {}}}" />`');
- hasNoDiagnostics(t, diagnostics);
-});
-
-tsTest("Event binding: Object with callable 'handleEvent' is bindable 2", t => {
- const { diagnostics } = getDiagnostics('function foo() {}; html``');
- hasNoDiagnostics(t, diagnostics);
-});
-
-tsTest("Event binding: Object with called 'handleEvent' is not bindable", t => {
- const { diagnostics } = getDiagnostics('function foo() {}; html``');
- hasDiagnostic(t, diagnostics, "no-noncallable-event-binding");
-});
-
-tsTest("Event binding: Object literal without 'handleEvent' is not bindable", t => {
- const { diagnostics } = getDiagnostics('function foo() {}; html``');
- hasDiagnostic(t, diagnostics, "no-noncallable-event-binding");
-});
-
-tsTest("Event binding: Mixed value binding with first expression being callable is bindable", t => {
- const { diagnostics } = getDiagnostics('html``');
- hasNoDiagnostics(t, diagnostics);
-});
diff --git a/packages/ts-lit-plugin/README.md b/packages/ts-lit-plugin/README.md
index a4905df5..ab09fe98 100644
--- a/packages/ts-lit-plugin/README.md
+++ b/packages/ts-lit-plugin/README.md
@@ -129,7 +129,6 @@ Each rule can have severity of `off`, `warning` or `error`. You can toggle rules
| :------ | ----------- | --------------- | --------------- |
| [no-invalid-boolean-binding](#-no-invalid-boolean-binding) | Disallow boolean attribute bindings on non-boolean types. | error | error |
| [no-expressionless-property-binding](#-no-expressionless-property-binding) | Disallow property bindings without an expression. | error | error |
-| [no-noncallable-event-binding](#-no-noncallable-event-binding) | Disallow event listener bindings with a noncallable type. | error | error |
| [no-boolean-in-attribute-binding](#-no-boolean-in-attribute-binding) | Disallow attribute bindings with a boolean type. | error | error |
| [no-complex-attribute-binding](#-no-complex-attribute-binding) | Disallow attribute bindings with a complex type. | error | error |
| [no-nullable-attribute-binding](#-no-nullable-attribute-binding) | Disallow attribute bindings with nullable types such as "null" or "undefined". | error | error |
@@ -394,26 +393,6 @@ The following example is not considered a warning:
html``
```
-#### 🌀 no-noncallable-event-binding
-
-It's a common mistake to incorrectly call the function when setting up an event handler binding instead of passing a reference to the function. This makes the function call whenever the code evaluates.
-
-The following examples are considered warnings:
-
-
-```js
-html``
-html``
-```
-
-The following examples are not considered warnings:
-
-
-```js
-html``
-html``
-```
-
#### 😈 no-boolean-in-attribute-binding
You should not be binding to a boolean type using an attribute binding because it could result in binding the string "true" or "false". Instead you should be using a **boolean** attribute binding.
@@ -484,6 +463,8 @@ html``
html``
html``
html``
+html``
+html``
```
The following examples are not considered warnings:
@@ -494,6 +475,8 @@ html``
html``
html``
html``
+html``
+html``
```
#### 💥 no-invalid-directive-binding
diff --git a/packages/vscode-lit-plugin/README.md b/packages/vscode-lit-plugin/README.md
index 8dce4cf0..1c6d1925 100644
--- a/packages/vscode-lit-plugin/README.md
+++ b/packages/vscode-lit-plugin/README.md
@@ -65,7 +65,6 @@ Each rule can have severity of `off`, `warning` or `error`. You can toggle rules
| :------ | ----------- | --------------- | --------------- |
| [no-invalid-boolean-binding](#-no-invalid-boolean-binding) | Disallow boolean attribute bindings on non-boolean types. | error | error |
| [no-expressionless-property-binding](#-no-expressionless-property-binding) | Disallow property bindings without an expression. | error | error |
-| [no-noncallable-event-binding](#-no-noncallable-event-binding) | Disallow event listener bindings with a noncallable type. | error | error |
| [no-boolean-in-attribute-binding](#-no-boolean-in-attribute-binding) | Disallow attribute bindings with a boolean type. | error | error |
| [no-complex-attribute-binding](#-no-complex-attribute-binding) | Disallow attribute bindings with a complex type. | error | error |
| [no-nullable-attribute-binding](#-no-nullable-attribute-binding) | Disallow attribute bindings with nullable types such as "null" or "undefined". | error | error |
@@ -330,26 +329,6 @@ The following example is not considered a warning:
html``
```
-#### 🌀 no-noncallable-event-binding
-
-It's a common mistake to incorrectly call the function when setting up an event handler binding instead of passing a reference to the function. This makes the function call whenever the code evaluates.
-
-The following examples are considered warnings:
-
-
-```js
-html``
-html``
-```
-
-The following examples are not considered warnings:
-
-
-```js
-html``
-html``
-```
-
#### 😈 no-boolean-in-attribute-binding
You should not be binding to a boolean type using an attribute binding because it could result in binding the string "true" or "false". Instead you should be using a **boolean** attribute binding.
@@ -420,6 +399,8 @@ html``
html``
html``
html``
+html``
+html``
```
The following examples are not considered warnings:
@@ -430,6 +411,8 @@ html``
html``
html``
html``
+html``
+html``
```
#### 💥 no-invalid-directive-binding
diff --git a/packages/vscode-lit-plugin/package.json b/packages/vscode-lit-plugin/package.json
index a1d5faa1..ce986f88 100644
--- a/packages/vscode-lit-plugin/package.json
+++ b/packages/vscode-lit-plugin/package.json
@@ -285,17 +285,6 @@
"error"
]
},
- "lit-plugin.rules.no-noncallable-event-binding": {
- "type": "string",
- "description": "Disallow event listener bindings with a noncallable type.",
- "default": "default",
- "enum": [
- "default",
- "off",
- "warning",
- "error"
- ]
- },
"lit-plugin.rules.no-boolean-in-attribute-binding": {
"type": "string",
"description": "Disallow attribute bindings with a boolean type.",
diff --git a/packages/vscode-lit-plugin/schemas/tsconfig.schema.json b/packages/vscode-lit-plugin/schemas/tsconfig.schema.json
index 74ed81cb..c0167537 100644
--- a/packages/vscode-lit-plugin/schemas/tsconfig.schema.json
+++ b/packages/vscode-lit-plugin/schemas/tsconfig.schema.json
@@ -148,11 +148,6 @@
"description": "Disallow property bindings without an expression.",
"enum": ["off", "warning", "error"]
},
- "no-noncallable-event-binding": {
- "type": "string",
- "description": "Disallow event listener bindings with a noncallable type.",
- "enum": ["off", "warning", "error"]
- },
"no-boolean-in-attribute-binding": {
"type": "string",
"description": "Disallow attribute bindings with a boolean type.",