From 3b027f400e0e326596eedc2ee17ab45a8383080d Mon Sep 17 00:00:00 2001
From: Nathan Bierema <nbierema@gmail.com>
Date: Sat, 19 Sep 2020 19:56:40 -0400
Subject: [PATCH] feat(map2tree): convert to TypeScript (#638)

* map2tree

* Fix build
---
 packages/map2tree/.babelrc                    |  3 +-
 packages/map2tree/.eslintignore               |  3 +
 packages/map2tree/.eslintrc.js                | 29 ++++++
 packages/map2tree/jest.config.js              |  3 +
 packages/map2tree/package.json                | 71 ++++++-------
 packages/map2tree/src/index.js                | 79 ---------------
 packages/map2tree/src/index.ts                | 99 +++++++++++++++++++
 .../{map2tree.spec.js => map2tree.spec.ts}    |  4 +-
 packages/map2tree/test/tsconfig.json          |  4 +
 packages/map2tree/tsconfig.json               |  7 ++
 packages/map2tree/tsconfig.webpack.json       |  4 +
 ...ck.config.umd.js => webpack.config.umd.ts} | 11 ++-
 12 files changed, 191 insertions(+), 126 deletions(-)
 create mode 100644 packages/map2tree/.eslintignore
 create mode 100644 packages/map2tree/.eslintrc.js
 create mode 100644 packages/map2tree/jest.config.js
 delete mode 100755 packages/map2tree/src/index.js
 create mode 100644 packages/map2tree/src/index.ts
 rename packages/map2tree/test/{map2tree.spec.js => map2tree.spec.ts} (97%)
 mode change 100755 => 100644
 create mode 100644 packages/map2tree/test/tsconfig.json
 create mode 100644 packages/map2tree/tsconfig.json
 create mode 100644 packages/map2tree/tsconfig.webpack.json
 rename packages/map2tree/{webpack.config.umd.js => webpack.config.umd.ts} (63%)

diff --git a/packages/map2tree/.babelrc b/packages/map2tree/.babelrc
index 1320b9a3..5259cd24 100755
--- a/packages/map2tree/.babelrc
+++ b/packages/map2tree/.babelrc
@@ -1,3 +1,4 @@
 {
-  "presets": ["@babel/preset-env"]
+  "presets": ["@babel/preset-env", "@babel/preset-typescript"],
+  "plugins": ["@babel/plugin-proposal-class-properties"]
 }
diff --git a/packages/map2tree/.eslintignore b/packages/map2tree/.eslintignore
new file mode 100644
index 00000000..1d149abd
--- /dev/null
+++ b/packages/map2tree/.eslintignore
@@ -0,0 +1,3 @@
+examples
+lib
+dist
diff --git a/packages/map2tree/.eslintrc.js b/packages/map2tree/.eslintrc.js
new file mode 100644
index 00000000..88327738
--- /dev/null
+++ b/packages/map2tree/.eslintrc.js
@@ -0,0 +1,29 @@
+module.exports = {
+  extends: '../../.eslintrc',
+  overrides: [
+    {
+      files: ['*.ts'],
+      extends: '../../eslintrc.ts.base.json',
+      parserOptions: {
+        tsconfigRootDir: __dirname,
+        project: ['./tsconfig.json'],
+      },
+    },
+    {
+      files: ['test/*.ts'],
+      extends: '../../eslintrc.ts.jest.base.json',
+      parserOptions: {
+        tsconfigRootDir: __dirname,
+        project: ['./test/tsconfig.json'],
+      },
+    },
+    {
+      files: ['webpack.config.umd.ts'],
+      extends: '../../eslintrc.ts.base.json',
+      parserOptions: {
+        tsconfigRootDir: __dirname,
+        project: ['./tsconfig.webpack.json'],
+      },
+    },
+  ],
+};
diff --git a/packages/map2tree/jest.config.js b/packages/map2tree/jest.config.js
new file mode 100644
index 00000000..8824c114
--- /dev/null
+++ b/packages/map2tree/jest.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+  preset: 'ts-jest',
+};
diff --git a/packages/map2tree/package.json b/packages/map2tree/package.json
index ee8d4cf3..04d1eb48 100755
--- a/packages/map2tree/package.json
+++ b/packages/map2tree/package.json
@@ -2,20 +2,6 @@
   "name": "map2tree",
   "version": "1.4.2",
   "description": "Utility for mapping maps to trees",
-  "main": "lib/index.js",
-  "scripts": {
-    "clean": "rimraf lib dist",
-    "build": "babel src --out-dir lib",
-    "build:umd": "webpack --progress --config webpack.config.umd.js",
-    "build:umd:min": "webpack --env.production --progress --config webpack.config.umd.js",
-    "test": "jest",
-    "prepare": "npm run build && npm run build:umd",
-    "prepublishOnly": "npm run test && npm run clean && npm run build && npm run build:umd && npm run build:umd:min"
-  },
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/reduxjs/redux-devtools.git"
-  },
   "keywords": [
     "map2tree",
     "map-to-tree",
@@ -23,38 +9,43 @@
     "map",
     "tree"
   ],
-  "author": "romseguy",
-  "license": "MIT",
+  "homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/map2tree",
   "bugs": {
     "url": "https://github.com/reduxjs/redux-devtools/issues"
   },
-  "homepage": "https://github.com/reduxjs/redux-devtools",
-  "devDependencies": {
-    "@babel/cli": "^7.10.5",
-    "@babel/core": "^7.11.1",
-    "@babel/preset-env": "^7.11.0",
-    "babel-loader": "^8.1.0",
-    "immutable": "^4.0.0-rc.12",
-    "jest": "^26.2.2",
-    "rimraf": "^3.0.2",
-    "webpack": "^4.44.1",
-    "webpack-cli": "^3.3.12"
-  },
-  "dependencies": {
-    "lodash": "^4.17.19"
-  },
-  "npmName": "map2tree",
+  "license": "MIT",
+  "author": "romseguy",
   "files": [
     "lib",
     "dist",
     "src"
   ],
-  "npmFileMap": [
-    {
-      "basePath": "/dist/",
-      "files": [
-        "*.js"
-      ]
-    }
-  ]
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/reduxjs/redux-devtools.git"
+  },
+  "scripts": {
+    "build": "npm run build:types && npm run build:js && npm run build:umd && npm run build:umd:min",
+    "build:types": "tsc --emitDeclarationOnly",
+    "build:js": "babel src --out-dir lib --extensions \".ts\" --source-maps inline",
+    "build:umd": "webpack --progress --config webpack.config.umd.ts",
+    "build:umd:min": "webpack --env.production --progress --config webpack.config.umd.ts",
+    "clean": "rimraf lib dist",
+    "test": "jest",
+    "lint": "eslint . --ext .ts",
+    "lint:fix": "eslint . --ext .ts --fix",
+    "type-check": "tsc --noEmit",
+    "type-check:watch": "npm run type-check -- --watch",
+    "preversion": "npm run type-check && npm run lint && npm run test",
+    "prepublishOnly": "npm run clean && npm run build"
+  },
+  "dependencies": {
+    "lodash": "^4.17.19"
+  },
+  "devDependencies": {
+    "@types/lodash": "^4.14.159",
+    "immutable": "^4.0.0-rc.12"
+  }
 }
diff --git a/packages/map2tree/src/index.js b/packages/map2tree/src/index.js
deleted file mode 100755
index 4e43da70..00000000
--- a/packages/map2tree/src/index.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import isArray from 'lodash/isArray';
-import isPlainObject from 'lodash/isPlainObject';
-import mapValues from 'lodash/mapValues';
-
-function visit(parent, visitFn, childrenFn) {
-  if (!parent) return;
-
-  visitFn(parent);
-
-  const children = childrenFn(parent);
-  if (children) {
-    const count = children.length;
-    for (let i = 0; i < count; i++) {
-      visit(children[i], visitFn, childrenFn);
-    }
-  }
-}
-
-function getNode(tree, key) {
-  let node = null;
-
-  visit(
-    tree,
-    (d) => {
-      if (d.name === key) {
-        node = d;
-      }
-    },
-    (d) => d.children
-  );
-
-  return node;
-}
-
-export default function map2tree(
-  root,
-  options = {},
-  tree = { name: options.key || 'state', children: [] }
-) {
-  if (!isPlainObject(root) && root && !root.toJS) {
-    return {};
-  }
-
-  const { key: rootNodeKey = 'state', pushMethod = 'push' } = options;
-  const currentNode = getNode(tree, rootNodeKey);
-
-  if (currentNode === null) {
-    return {};
-  }
-
-  mapValues(root && root.toJS ? root.toJS() : root, (maybeImmutable, key) => {
-    const value =
-      maybeImmutable && maybeImmutable.toJS
-        ? maybeImmutable.toJS()
-        : maybeImmutable;
-    let newNode = { name: key };
-
-    if (isArray(value)) {
-      newNode.children = [];
-
-      for (let i = 0; i < value.length; i++) {
-        newNode.children[pushMethod]({
-          name: `${key}[${i}]`,
-          [isPlainObject(value[i]) ? 'object' : 'value']: value[i],
-        });
-      }
-    } else if (isPlainObject(value)) {
-      newNode.children = [];
-    } else {
-      newNode.value = value;
-    }
-
-    currentNode.children[pushMethod](newNode);
-
-    map2tree(value, { key, pushMethod }, tree);
-  });
-
-  return tree;
-}
diff --git a/packages/map2tree/src/index.ts b/packages/map2tree/src/index.ts
new file mode 100644
index 00000000..958eef3d
--- /dev/null
+++ b/packages/map2tree/src/index.ts
@@ -0,0 +1,99 @@
+import isArray from 'lodash/isArray';
+import isPlainObject from 'lodash/isPlainObject';
+import mapValues from 'lodash/mapValues';
+
+interface Node {
+  name: string;
+  children?: Node[];
+  value?: unknown;
+}
+
+function visit(
+  parent: Node,
+  visitFn: (parent: Node) => void,
+  childrenFn: (parent: Node) => Node[] | undefined
+) {
+  if (!parent) return;
+
+  visitFn(parent);
+
+  const children = childrenFn(parent);
+  if (children) {
+    const count = children.length;
+    for (let i = 0; i < count; i++) {
+      visit(children[i], visitFn, childrenFn);
+    }
+  }
+}
+
+function getNode(tree: Node, key: string): Node | null {
+  let node = null;
+
+  visit(
+    tree,
+    (d) => {
+      if (d.name === key) {
+        node = d;
+      }
+    },
+    (d) => d.children
+  );
+
+  return node;
+}
+
+export default function map2tree(
+  // eslint-disable-next-line @typescript-eslint/ban-types
+  root: {},
+  options: { key?: string; pushMethod?: 'push' | 'unshift' } = {},
+  tree: Node = { name: options.key || 'state', children: [] }
+): Node {
+  // eslint-disable-next-line @typescript-eslint/ban-types
+  if (!isPlainObject(root) && root && !(root as { toJS: () => {} }).toJS) {
+    return {} as Node;
+  }
+
+  const { key: rootNodeKey = 'state', pushMethod = 'push' } = options;
+  const currentNode = getNode(tree, rootNodeKey);
+
+  if (currentNode === null) {
+    return {} as Node;
+  }
+
+  mapValues(
+    // eslint-disable-next-line @typescript-eslint/ban-types
+    root && (root as { toJS: () => {} }).toJS
+      ? // eslint-disable-next-line @typescript-eslint/ban-types
+        (root as { toJS: () => {} }).toJS()
+      : root,
+    // eslint-disable-next-line @typescript-eslint/ban-types
+    (maybeImmutable: { toJS?: () => {} }, key) => {
+      const value =
+        maybeImmutable && maybeImmutable.toJS
+          ? maybeImmutable.toJS()
+          : maybeImmutable;
+      const newNode: Node = { name: key };
+
+      if (isArray(value)) {
+        newNode.children = [];
+
+        for (let i = 0; i < value.length; i++) {
+          newNode.children[pushMethod]({
+            name: `${key}[${i}]`,
+            [isPlainObject(value[i]) ? 'object' : 'value']: value[i],
+          });
+        }
+      } else if (isPlainObject(value)) {
+        newNode.children = [];
+      } else {
+        newNode.value = value;
+      }
+
+      currentNode.children![pushMethod](newNode);
+
+      map2tree(value, { key, pushMethod }, tree);
+    }
+  );
+
+  return tree;
+}
diff --git a/packages/map2tree/test/map2tree.spec.js b/packages/map2tree/test/map2tree.spec.ts
old mode 100755
new mode 100644
similarity index 97%
rename from packages/map2tree/test/map2tree.spec.js
rename to packages/map2tree/test/map2tree.spec.ts
index 7114a16e..e1cc3787
--- a/packages/map2tree/test/map2tree.spec.js
+++ b/packages/map2tree/test/map2tree.spec.ts
@@ -1,5 +1,5 @@
 import map2tree from '../src';
-import immutable from 'immutable';
+import * as immutable from 'immutable';
 
 test('# rootNodeKey', () => {
   const map = {};
@@ -151,7 +151,7 @@ describe('# array map', () => {
   });
 
   test('## unshift', () => {
-    const options = { pushMethod: 'unshift' };
+    const options = { pushMethod: 'unshift' as const };
     const expected = {
       name: 'state',
       children: [
diff --git a/packages/map2tree/test/tsconfig.json b/packages/map2tree/test/tsconfig.json
new file mode 100644
index 00000000..b55532d2
--- /dev/null
+++ b/packages/map2tree/test/tsconfig.json
@@ -0,0 +1,4 @@
+{
+  "extends": "../../../tsconfig.base.json",
+  "include": ["../src", "."]
+}
diff --git a/packages/map2tree/tsconfig.json b/packages/map2tree/tsconfig.json
new file mode 100644
index 00000000..7b7d1492
--- /dev/null
+++ b/packages/map2tree/tsconfig.json
@@ -0,0 +1,7 @@
+{
+  "extends": "../../tsconfig.react.base.json",
+  "compilerOptions": {
+    "outDir": "lib"
+  },
+  "include": ["src"]
+}
diff --git a/packages/map2tree/tsconfig.webpack.json b/packages/map2tree/tsconfig.webpack.json
new file mode 100644
index 00000000..655c4644
--- /dev/null
+++ b/packages/map2tree/tsconfig.webpack.json
@@ -0,0 +1,4 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "include": ["webpack.config.umd.ts"]
+}
diff --git a/packages/map2tree/webpack.config.umd.js b/packages/map2tree/webpack.config.umd.ts
similarity index 63%
rename from packages/map2tree/webpack.config.umd.js
rename to packages/map2tree/webpack.config.umd.ts
index fe011338..6bb5dabc 100644
--- a/packages/map2tree/webpack.config.umd.js
+++ b/packages/map2tree/webpack.config.umd.ts
@@ -1,9 +1,9 @@
-const path = require('path');
+import * as path from 'path';
 
-module.exports = (env = {}) => ({
+module.exports = (env: { production?: boolean } = {}) => ({
   mode: env.production ? 'production' : 'development',
   entry: {
-    app: ['./src/index.js'],
+    app: ['./src/index'],
   },
   output: {
     library: 'd3tooltip',
@@ -14,10 +14,13 @@ module.exports = (env = {}) => ({
   module: {
     rules: [
       {
-        test: /\.js$/,
+        test: /\.(js|ts)$/,
         loader: 'babel-loader',
         exclude: /node_modules/,
       },
     ],
   },
+  resolve: {
+    extensions: ['.js', '.jsx', '.ts', '.tsx'],
+  },
 });