Improive typing in modern

This commit is contained in:
Jordan Eldredge 2019-09-19 07:03:31 -07:00
parent 5f870c870b
commit 7652381d46
5 changed files with 80 additions and 31 deletions

View file

@ -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 = [];
}
}

View file

@ -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);
}

View file

@ -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?

View file

@ -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<T extends { children: T[] }>(
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<XmlNode> {
export async function readXml(
zip: JSZip,
filepath: string
): Promise<XmlNode | null> {
const file = await getCaseInsensitveFile(zip, filepath);
if (file == null) {
return null;
@ -65,7 +69,7 @@ export async function readXml(zip: JSZip, filepath: string): Promise<XmlNode> {
export async function readUint8array(
zip: JSZip,
filepath: string
): Promise<Uint8Array> {
): Promise<Uint8Array | null> {
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<T>(arr: Array<T | Array<T>>): Array<T> {
const newArr = [];
function flatten<T>(arr: Array<T | T[]>): T[] {
const newArr: T[] = [];
arr.forEach(item => {
if (Array.isArray(item)) {
newArr.push(...item);
@ -92,8 +96,8 @@ function flatten<T>(arr: Array<T | Array<T>>): Array<T> {
// interpoates the resulting flat array into the top level array of results.
export async function asyncFlatMap<T, R>(
arr: Array<T>,
mapper: (value: T) => R | Array<T>
): Promise<Array<R>> {
mapper: (value: T) => Promise<T[] | R>
): Promise<R[]> {
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<T, R>(
//
// 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<T extends { children: T[] }>(
node: T,
mapper: (node: T) => Promise<T[] | T>
): Promise<T> {
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<T extends { children: T[] }>(
node: T,
predicate: (candidate: T) => boolean
): number[] {
): number[] | null {
if (predicate(node)) {
return [];
}
@ -179,7 +195,7 @@ export function findPathToNode<T extends { children: T[] }>(
// Bredth-first search in a tree
export function findInTree<T extends { children: T[] }>(
node: T,
predicate
predicate: (node: T) => boolean
): T | null {
if (predicate(node)) {
return node;
@ -212,20 +228,22 @@ export function findParent<T extends { parent: T | null }>(
// Operations on trees
export function findParentNodeOfType<
T extends { parent: T | null; name: string }
>(node: T, type: Set<string>): T {
>(node: T, type: Set<string>): T | null {
return findParent(node, n => type.has(n.name));
}
export function findParentOrCurrentNodeOfType<
T extends { parent: T | null; name: string }
>(node: T, type: Set<string>): T {
>(node: T, type: Set<string>): 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<T> {
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<T extends { children: T[]; parent: T | undefined }>(
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 <Elements> nodes that are in node's lexical scope.
// return the first child of an <Elements> 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 <GroupDef> 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 };
}

View file

@ -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"]
}