From 7652381d46baee3dd150e54df6dad6ca7f70d298 Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Thu, 19 Sep 2019 07:03:31 -0700 Subject: [PATCH] Improive typing in modern --- modern/src/Emitter.ts | 4 +- modern/src/runtime/MakiObject.ts | 6 +-- modern/src/types.ts | 4 +- modern/src/utils.ts | 88 +++++++++++++++++++++++--------- tsconfig.json | 9 +++- 5 files changed, 80 insertions(+), 31 deletions(-) diff --git a/modern/src/Emitter.ts b/modern/src/Emitter.ts index 46f52909..33d21719 100644 --- a/modern/src/Emitter.ts +++ b/modern/src/Emitter.ts @@ -37,7 +37,7 @@ export default class Emitter { dispose() { // Note: This will cause any future trigger or hook to cause a runtime error. - this._hooks = null; - this._globalHooks = null; + this._hooks = {}; + this._globalHooks = []; } } diff --git a/modern/src/runtime/MakiObject.ts b/modern/src/runtime/MakiObject.ts index 8ce92a0b..b2069415 100644 --- a/modern/src/runtime/MakiObject.ts +++ b/modern/src/runtime/MakiObject.ts @@ -58,15 +58,15 @@ class MakiObject { this.parent.js_trigger("js_update"); } - js_trigger(eventName, ...args) { + js_trigger(eventName: string, ...args: any[]): void { this._emitter.trigger(eventName.toLowerCase(), ...args); } - js_listen(eventName, cb) { + js_listen(eventName: string, cb: (...args: any[]) => void) { return this._emitter.listen(eventName, cb); } - js_listenToAll(cb) { + js_listenToAll(cb: (eventName: string, ...args: any[]) => void) { return this._emitter.listenToAll(cb); } diff --git a/modern/src/types.ts b/modern/src/types.ts index 3373aeb4..5f7b4a72 100644 --- a/modern/src/types.ts +++ b/modern/src/types.ts @@ -3,9 +3,9 @@ export type MakiTree = any; export type XmlNode = { children: XmlNode[]; - attributes: Object; + attributes: { id?: string; file?: string }; name: string; - uid: number; + uid: number; // TODO: They don't start with these. Do we need types for each stage? }; // What is a tree, but a single root node? diff --git a/modern/src/utils.ts b/modern/src/utils.ts index 9abe3cd5..3120b805 100644 --- a/modern/src/utils.ts +++ b/modern/src/utils.ts @@ -1,6 +1,7 @@ import JSZip from "jszip"; import { xml2js } from "xml-js"; import { XmlNode } from "./types"; +import MakiObject from "./runtime/MakiObject"; let nextId = 0; export function getId(): number { @@ -14,7 +15,7 @@ export function getId(): number { export function mapTreeBreadth( node: T, cb: (node: T, parent: T) => T -) { +): T { const children = node.children || []; const mappedChildren = children.map(child => { const newChild = cb(child, node); @@ -49,7 +50,10 @@ export function getCaseInsensitveFile( } // Read a -export async function readXml(zip: JSZip, filepath: string): Promise { +export async function readXml( + zip: JSZip, + filepath: string +): Promise { const file = await getCaseInsensitveFile(zip, filepath); if (file == null) { return null; @@ -65,7 +69,7 @@ export async function readXml(zip: JSZip, filepath: string): Promise { export async function readUint8array( zip: JSZip, filepath: string -): Promise { +): Promise { const file = await getCaseInsensitveFile(zip, filepath); if (file == null) { return null; @@ -75,8 +79,8 @@ export async function readUint8array( // I any of the values in `arr` are themselves arrays, interpolate the nested // array into the top level array. -function flatten(arr: Array>): Array { - const newArr = []; +function flatten(arr: Array): T[] { + const newArr: T[] = []; arr.forEach(item => { if (Array.isArray(item)) { newArr.push(...item); @@ -92,8 +96,8 @@ function flatten(arr: Array>): Array { // interpoates the resulting flat array into the top level array of results. export async function asyncFlatMap( arr: Array, - mapper: (value: T) => R | Array -): Promise> { + mapper: (value: T) => Promise +): Promise { const mapped = await Promise.all(arr.map(mapper)); const childPromises = mapped.map(async item => { if (Array.isArray(item)) { @@ -113,7 +117,10 @@ export async function asyncFlatMap( // // Note: The root node will not be transformed by the mapper, since the mapper // could potentially return multiple nodes. -export async function asyncTreeFlatMap(node, mapper) { +export async function asyncTreeFlatMap( + node: T, + mapper: (node: T) => Promise +): Promise { const { children } = node; if (children == null) { return node; @@ -122,7 +129,13 @@ export async function asyncTreeFlatMap(node, mapper) { const mappedChildren = await asyncFlatMap(children, mapper); const recursedChildren = await Promise.all( - mappedChildren.map(child => asyncTreeFlatMap(child, mapper)) + mappedChildren.map(child => + asyncTreeFlatMap( + // @ts-ignore FixMe + child, + mapper + ) + ) ); return { ...node, children: recursedChildren }; @@ -138,6 +151,9 @@ export async function inlineIncludes( if (node.name !== "include") { return node; } + if (node.attributes.file == null) { + return node; + } // TODO: Normalize file names so that they hit the same cache const includedFile = await readXml(zip, node.attributes.file); if (includedFile == null) { @@ -160,7 +176,7 @@ export function unimplementedWarning(name: string): void { export function findPathToNode( node: T, predicate: (candidate: T) => boolean -): number[] { +): number[] | null { if (predicate(node)) { return []; } @@ -179,7 +195,7 @@ export function findPathToNode( // Bredth-first search in a tree export function findInTree( node: T, - predicate + predicate: (node: T) => boolean ): T | null { if (predicate(node)) { return node; @@ -212,20 +228,22 @@ export function findParent( // Operations on trees export function findParentNodeOfType< T extends { parent: T | null; name: string } ->(node: T, type: Set): T { +>(node: T, type: Set): T | null { return findParent(node, n => type.has(n.name)); } export function findParentOrCurrentNodeOfType< T extends { parent: T | null; name: string } ->(node: T, type: Set): T { +>(node: T, type: Set): T | null { if (type.has(node.name)) { return node; } return findParentNodeOfType(node, type); } -export function findDescendantByTypeAndId(node, type, id) { +export function findDescendantByTypeAndId< + T extends { children: T[]; name: string; attributes?: { id?: string } } +>(node: T, type: string, id: string): T | null { if (node.children.length === 0) { return null; } @@ -235,6 +253,7 @@ export function findDescendantByTypeAndId(node, type, id) { const child = node.children[i]; if ( (!type || child.name === type) && + child.attributes && (child.attributes.id !== undefined && child.attributes.id.toLowerCase() === lowerCaseId) ) { @@ -253,14 +272,22 @@ export function findDescendantByTypeAndId(node, type, id) { return null; } -function findDirectDescendantById(node, id) { +function findDirectDescendantById< + T extends { children: T[]; attributes?: { id?: string } } +>(node: T, id: string): T | undefined { const lowerCaseId = id.toLowerCase(); - return node.children.find( - item => item.attributes && item.attributes.id.toLowerCase() === lowerCaseId + return node.children.find(item => + Boolean( + item.attributes && + item.attributes.id && + item.attributes.id.toLowerCase() === lowerCaseId + ) ); } -function* iterateLexicalScope(node) { +function* iterateLexicalScope< + T extends { children: T[]; parent: T | undefined } +>(node: T): IterableIterator { let currentNode = node; while (currentNode.parent) { const { parent } = currentNode; @@ -277,7 +304,10 @@ function* iterateLexicalScope(node) { } // Search up the tree for a node that is in `node`'s lexical that matches `predicate`. -function findInLexicalScope(node, predicate) { +function findInLexicalScope( + node: T, + predicate: (node: T) => boolean +): T | null { for (const child of iterateLexicalScope(node)) { if (predicate(child)) { return child; @@ -288,7 +318,10 @@ function findInLexicalScope(node, predicate) { // Search up the tree for nodes that are in node's lexical scope. // return the first child of an that matches id -export function findElementById(node, id) { +export function findElementById( + node: MakiObject, + id: string +): MakiObject | null { for (const child of iterateLexicalScope(node)) { if (child.getclassname && child.getclassname() === "Elements") { const element = findDirectDescendantById(child, id); @@ -301,11 +334,15 @@ export function findElementById(node, id) { } // Search up the tree for a node that is in node's lexical scope and matches id. -export function findGroupDefById(node, id) { +export function findGroupDefById( + node: MakiObject, + id: String +): MakiObject | null { return findInLexicalScope(node, child => { return ( child.getclassname && child.getclassname() === "GroupDef" && + // @ts-ignore We don't have good typing for attributes child.attributes.id === id ); }); @@ -316,7 +353,11 @@ export function findGroupDefById(node, id) { // node first, in that case we didn't find the element // TODO: this might be overly generous, including some definitions that // shouldn't be accessible. But it's working for now :-X -export function findXmlElementById(node, id, root) { +export function findXmlElementById( + node: XmlNode, + id: string, + root: XmlNode +): XmlNode | null { if (root.uid === node.uid) { // Search ends if we find the node that initiated the search, since it means we weren't able to // find the match in its scope @@ -342,6 +383,7 @@ export function findXmlElementById(node, id, root) { } } } + return null; } // This is intentionally async since we may want to sub it out for an async @@ -388,7 +430,7 @@ export async function getSizeFromUrl( } let mousePosition = { x: 0, y: 0 }; -function handleMouseMove(e) { +function handleMouseMove(e: MouseEvent): void { mousePosition = { x: e.clientX, y: e.clientY }; } diff --git a/tsconfig.json b/tsconfig.json index 94bcd38e..75bb7f4e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,13 @@ "noEmit": true, "pretty": true }, - "include": ["js/**/*.js", "js/**/*.ts", "js/**/*.tsx"], + "include": [ + "js/**/*.js", + "js/**/*.ts", + "js/**/*.tsx", + "modern/src/utils.ts", + "modern/src/Emitter.ts", + "modern/src/runtime/MakiObject.ts" + ], "exclude": ["node_modules", "**/*.spec.ts", "demo/built"] }