From cc2eccac0a7bb778c95dac866b84bb38b9235f46 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Wed, 7 Dec 2022 17:58:02 +0100 Subject: [PATCH 1/2] Polymer analyzer: add event parsing when a property is defined with notify true --- .../flavors/lit-element/discover-events.ts | 91 +++++++++++++++++++ .../flavors/lit-element/lit-element-flavor.ts | 4 +- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/analyze/flavors/lit-element/discover-events.ts diff --git a/src/analyze/flavors/lit-element/discover-events.ts b/src/analyze/flavors/lit-element/discover-events.ts new file mode 100644 index 00000000..90fc7cce --- /dev/null +++ b/src/analyze/flavors/lit-element/discover-events.ts @@ -0,0 +1,91 @@ +import { Node, ReturnStatement } from "typescript"; +import { ComponentEvent } from "../../types/features/component-event"; +import { hasModifier } from "../../util/ast-util"; +import { AnalyzerDeclarationVisitContext } from "../analyzer-flavor"; +import { resolveNodeValue } from "../../util/resolve-node-value"; +import { camelToDashCase } from "../../util/text-util"; + +/** + * Discovers events dispatched + * @param node + * @param context + */ +export function discoverEvents(node: Node, context: AnalyzerDeclarationVisitContext): ComponentEvent[] | undefined { + const { ts } = context; + + // Never pick up members not declared directly on the declaration node being traversed + if (node.parent !== context.declarationNode) { + return undefined; + } + + // Polymer notify on properties + // static get properties() { return { myProp: {notify: true} } } + // https://polymer-library.polymer-project.org/3.0/docs/devguide/data-system#change-events + if (ts.isGetAccessor(node) && hasModifier(node, ts.SyntaxKind.StaticKeyword)) { + const name = node.name.getText(); + if (name === "properties" && node.body != null) { + const returnStatement = node.body.statements.find(ts.isReturnStatement.bind(ts)); + if (returnStatement != null) { + return parseStaticProperties(returnStatement, context); + } + } + } + + return undefined; +} + +/** + * Visits static properties + * static get properties() { return { myProp: {type: String, notify: true} } } + * @param returnStatement + * @param context + */ +function parseStaticProperties(returnStatement: ReturnStatement, context: AnalyzerDeclarationVisitContext): ComponentEvent[] | undefined { + const { ts } = context; + + const eventsResults: ComponentEvent[] = []; + + if (returnStatement.expression != null && ts.isObjectLiteralExpression(returnStatement.expression)) { + // Each property in the object literal expression corresponds to a class field. + for (const propNode of returnStatement.expression.properties) { + // Get propName + const propName = propNode.name != null && ts.isIdentifier(propNode.name) ? propNode.name.text : undefined; + + if (propName && ts.isPropertyAssignment(propNode)) { + const resolved = resolveNodeValue(propNode.initializer, context); + + if (resolved && typeof resolved.value === "object" && resolved.value !== null && !Array.isArray(resolved.value)) { + if (hasOwnProperty(resolved.value, "notify") && resolved.value.notify !== undefined) { + if (resolved.value.notify) { + eventsResults.push({ + jsDoc: { + description: "Fired when the `" + propName + "` property changes", + tags: [ + { + tag: "type", + parsed: () => { + return { + tag: "type", + type: "CustomEvent<{value: *, path: ?string}>", + className: "CustomEvent" + }; + } + } + ] + }, + name: camelToDashCase(propName).toLowerCase() + "-changed", + node: propNode + }); + } + } + } + } + } + } + + return eventsResults; +} + +function hasOwnProperty(obj: object, key: T): obj is { [K in T]: unknown } { + return Object.prototype.hasOwnProperty.call(obj, key); +} diff --git a/src/analyze/flavors/lit-element/lit-element-flavor.ts b/src/analyze/flavors/lit-element/lit-element-flavor.ts index 78853392..72d86a97 100644 --- a/src/analyze/flavors/lit-element/lit-element-flavor.ts +++ b/src/analyze/flavors/lit-element/lit-element-flavor.ts @@ -3,6 +3,7 @@ import { discoverDefinitions } from "./discover-definitions"; import { discoverMembers } from "./discover-members"; import { excludeNode } from "./exclude-node"; import { refineFeature } from "./refine-feature"; +import { discoverEvents } from "./discover-events"; /** * Flavors for analyzing LitElement related features: https://lit-element.polymer-project.org/ @@ -13,7 +14,8 @@ export class LitElementFlavor implements AnalyzerFlavor { discoverDefinitions = discoverDefinitions; discoverFeatures = { - member: discoverMembers + member: discoverMembers, + event: discoverEvents }; refineFeature = refineFeature; From 8a585f7476c46d1f8c289f0c872b0e5c393fb880 Mon Sep 17 00:00:00 2001 From: jpradelle Date: Thu, 19 Oct 2023 15:46:19 +0200 Subject: [PATCH 2/2] Fix merge breaking changes --- src/analyze/flavors/lit-element/discover-events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyze/flavors/lit-element/discover-events.ts b/src/analyze/flavors/lit-element/discover-events.ts index 90fc7cce..c88be234 100644 --- a/src/analyze/flavors/lit-element/discover-events.ts +++ b/src/analyze/flavors/lit-element/discover-events.ts @@ -21,7 +21,7 @@ export function discoverEvents(node: Node, context: AnalyzerDeclarationVisitCont // Polymer notify on properties // static get properties() { return { myProp: {notify: true} } } // https://polymer-library.polymer-project.org/3.0/docs/devguide/data-system#change-events - if (ts.isGetAccessor(node) && hasModifier(node, ts.SyntaxKind.StaticKeyword)) { + if (ts.isGetAccessor(node) && hasModifier(node, ts.SyntaxKind.StaticKeyword, ts)) { const name = node.name.getText(); if (name === "properties" && node.body != null) { const returnStatement = node.body.statements.find(ts.isReturnStatement.bind(ts));