From d860f5cabeabe2cdeb12ea62be2e58c27815841b Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sun, 10 May 2020 15:22:01 +0100 Subject: [PATCH] add event bindings to incompatible type binding rule --- .../src/rules/no-incompatible-type-binding.ts | 2 ++ .../type/is-assignable-in-event-binding.ts | 36 +++++++++++++++++++ .../test/helpers/generate-test-file.ts | 3 +- .../rules/no-incompatible-type-binding.ts | 20 +++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 packages/lit-analyzer/src/rules/util/type/is-assignable-in-event-binding.ts 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 ebaff2ec..187ebf9e 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. @@ -33,6 +34,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/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..7c16906d --- /dev/null +++ b/packages/lit-analyzer/src/rules/util/type/is-assignable-in-event-binding.ts @@ -0,0 +1,36 @@ +import { SimpleType, SimpleTypeKind, toTypeString } from "ts-simple-type"; +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 { + const expectedType: SimpleType = { + kind: SimpleTypeKind.FUNCTION, + returnType: { kind: SimpleTypeKind.VOID }, + argTypes: [ + { + name: 'event', + type: typeA, + optional: false, + spread: false, + initializer: false + } + ] + }; + + if (!isAssignableToType({ typeA: expectedType, typeB }, context)) { + context.report({ + location: rangeFromHtmlNodeAttr(htmlAttr), + message: `Type '${toTypeString(typeB)}' is not assignable to '${toTypeString(expectedType)}'` + }); + + return false; + } + + return true; +} diff --git a/packages/lit-analyzer/test/helpers/generate-test-file.ts b/packages/lit-analyzer/test/helpers/generate-test-file.ts index 210f9d6b..2fff92b4 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 HTMElement { ${(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 50c47b90..5412f45a 100644 --- a/packages/lit-analyzer/test/rules/no-incompatible-type-binding.ts +++ b/packages/lit-analyzer/test/rules/no-incompatible-type-binding.ts @@ -175,3 +175,23 @@ test("Attribute binding: 'guard' directive correctly infers correct type from th const { diagnostics } = getDiagnostics('type guard = Function; html` ({} as string | number))}" />`'); hasDiagnostic(t, diagnostics, "no-incompatible-type-binding"); }); + +test("Event binding: event handler is assignable to valid event", t => { + const { diagnostics } = getDiagnostics([makeElement({ events: ["foo-event"] }), 'html` {}}>`']); + hasNoDiagnostics(t, diagnostics); +}); + +test("Event binding: event handler is assignable to valid typed event", t => { + const { diagnostics } = getDiagnostics([makeElement({ events: ["{MouseEvent} foo-event"] }), 'html` {}}>`']); + hasNoDiagnostics(t, diagnostics); +}); + +test("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"); +}); + +test("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"); +});