Skip to content

Commit

Permalink
Share common nodes within a single module
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown committed Dec 31, 2019
1 parent 60bbc51 commit 6b9d0c2
Show file tree
Hide file tree
Showing 8 changed files with 524 additions and 26 deletions.
5 changes: 5 additions & 0 deletions .changeset/kind-laws-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@magical-types/macro": minor
---

Share common nodes within a single module
5 changes: 5 additions & 0 deletions .changeset/slimy-zoos-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@magical-types/types": patch
---

Add `MagicalNodeWithIndexes` and `MagicalNodeIndex`
153 changes: 141 additions & 12 deletions packages/macro/runtime/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,148 @@
import { Types, PropTypes as PrettyPropTypes } from "@magical-types/pretty";
import * as React from "react";
import { MagicalNode } from "@magical-types/types";
import flatted from "flatted";

function parseStringified(val: string): MagicalNode {
try {
return flatted.parse(val);
} catch (err) {
console.error("error parsing stringified node:", val);
throw err;
}
import {
MagicalNode,
MagicalNodeWithIndexes,
MagicalNodeIndex,
TypeParameterNode
} from "@magical-types/types";

let wrapInCache = <Arg extends object, Return>(arg: (type: Arg) => Return) => {
let cache = new WeakMap<Arg, Return>();
return (type: Arg): Return => {
let cachedNode = cache.get(type);
if (cachedNode !== undefined) {
return cachedNode;
}
let obj = {} as Return;
cache.set(type, obj);
let node = arg(type);
Object.assign(obj, node);
return obj;
};
};

function parseStringified(
nodes: MagicalNodeWithIndexes[],
index: MagicalNodeIndex
): MagicalNode {
let getMagicalNodeWithIndexes = wrapInCache(
(node: MagicalNodeWithIndexes): MagicalNode => {
switch (node.type) {
case "StringLiteral":
case "NumberLiteral":
case "TypeParameter":
case "Symbol":
case "Intrinsic": {
return node;
}
case "Union": {
return {
type: "Union",
types: node.types.map(x => getNodeFromIndex(x)),
name: node.name
};
}
case "Intersection": {
return {
type: "Intersection",
types: node.types.map(x => getNodeFromIndex(x))
};
}
case "Array":
case "Promise":
case "ReadonlyArray": {
return {
type: node.type,
value: getNodeFromIndex(node.value)
};
}
case "Tuple": {
return {
type: "Tuple",
value: node.value.map(x => getNodeFromIndex(x))
};
}
case "IndexedAccess": {
return {
type: "IndexedAccess",
index: getNodeFromIndex(node.index),
object: getNodeFromIndex(node.object)
};
}
case "Class": {
return {
type: "Class",
name: node.name,
properties: node.properties.map(x => {
return { ...x, value: getNodeFromIndex(x.value) };
}),
thisNode: node.thisNode ? getNodeFromIndex(node.thisNode) : null,
typeParameters: node.typeParameters.map(x => getNodeFromIndex(x))
};
}
case "Object": {
return {
type: "Object",
name: node.name,
properties: node.properties.map(x => {
return { ...x, value: getNodeFromIndex(x.value) };
}),
callSignatures: node.callSignatures.map(x => {
return {
return: getNodeFromIndex(x.return),
parameters: x.parameters.map(x => ({
...x,
type: getNodeFromIndex(x.type)
})),
typeParameters: x.typeParameters.map(x =>
getNodeFromIndex(x)
) as TypeParameterNode[]
};
}),
constructSignatures: node.constructSignatures.map(x => {
return {
return: getNodeFromIndex(x.return),
parameters: x.parameters.map(x => ({
...x,
type: getNodeFromIndex(x.type)
})),
typeParameters: x.typeParameters.map(x =>
getNodeFromIndex(x)
) as TypeParameterNode[]
};
}),
aliasTypeArguments: node.aliasTypeArguments.map(x =>
getNodeFromIndex(x)
) as TypeParameterNode[]
};
}
case "Conditional": {
return {
type: "Conditional",
check: getNodeFromIndex(node.check),
extends: getNodeFromIndex(node.extends),
false: getNodeFromIndex(node.false),
true: getNodeFromIndex(node.true)
};
}

default: {
let _thisMakesTypeScriptEnsureThatAllNodesAreSpecifiedHere: never = node;
// @ts-ignore
throw new Error("this should never happen: " + node.type);
}
}
}
);

let getNodeFromIndex = (index: MagicalNodeIndex) =>
getMagicalNodeWithIndexes(nodes[index]);
return getNodeFromIndex(index);
}

let getMagicalNode = (props: any): MagicalNode => {
return parseStringified((props as any).__types);
return parseStringified((props as any).__types, props.__typeIndex);
};

export let FunctionTypes = (props: {
Expand All @@ -35,5 +164,5 @@ export let PropTypes = (props: {
};

export function getNode<Type>() {
return parseStringified(arguments[0]);
return parseStringified(arguments[0], arguments[1]);
}
70 changes: 58 additions & 12 deletions packages/macro/src/get-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { NodePath } from "@babel/core";
import * as BabelTypes from "@babel/types";
import { InternalError } from "@magical-types/errors";
import { Project } from "ts-morph";
import * as flatted from "flatted";
import { convertType, getPropTypesType } from "@magical-types/convert-type";
import { MagicalNode, MagicalNodeIndex } from "@magical-types/types/src";
import { serializeNodes } from "./serialize";

export function getTypes(
filename: string,
Expand All @@ -19,7 +20,8 @@ export function getTypes(
| { exportName: "getNode"; path: NodePath<BabelTypes.CallExpression> }
>
>,
numOfThings: number
numOfThings: number,
babelProgram: NodePath<BabelTypes.Program>
) {
let configFileName = typescript.findConfigFile(
filename,
Expand All @@ -38,6 +40,20 @@ export function getTypes(
let sourceFile = project.getSourceFileOrThrow(filename).compilerNode;
let typeChecker = project.getTypeChecker().compilerObject;

let rootNodes: MagicalNode[] = [];
let callbacks: ((
nodesArrayReference: string,
index: MagicalNodeIndex
) => void)[] = [];

let insertNode = (
node: MagicalNode,
cb: (nodesArrayReference: string, index: MagicalNodeIndex) => void
) => {
rootNodes.push(node);
callbacks.push(cb);
};

let num = 0;
let visit = (node: typescript.Node) => {
typescript.forEachChild(node, node => {
Expand Down Expand Up @@ -101,14 +117,22 @@ export function getTypes(
);
}
let converted = convertType(type, []);
val.path.node.attributes.push(
BabelTypes.jsxAttribute(
BabelTypes.jsxIdentifier("__types"),
BabelTypes.jsxExpressionContainer(
BabelTypes.stringLiteral(flatted.stringify(converted))
insertNode(converted, (nodesArrayReference, index) => {
val.path.node.attributes.push(
BabelTypes.jsxAttribute(
BabelTypes.jsxIdentifier("__typeIndex"),
BabelTypes.jsxExpressionContainer(
BabelTypes.numericLiteral(index)
)
),
BabelTypes.jsxAttribute(
BabelTypes.jsxIdentifier("__types"),
BabelTypes.jsxExpressionContainer(
BabelTypes.identifier(nodesArrayReference)
)
)
)
);
);
});
} else if (val.exportName === "getNode") {
if (!typescript.isCallExpression(node.parent)) {
throw new InternalError("not call expression for getNode");
Expand All @@ -119,9 +143,12 @@ export function getTypes(
);
let converted = convertType(type, []);

val.path.node.arguments.push(
BabelTypes.stringLiteral(flatted.stringify(converted))
);
insertNode(converted, (nodesArrayReference, index) => {
val.path.node.arguments.push(
BabelTypes.identifier(nodesArrayReference),
BabelTypes.numericLiteral(index)
);
});
} else {
throw new InternalError("unexpected node type");
}
Expand All @@ -135,4 +162,23 @@ export function getTypes(
if (num !== numOfThings) {
throw new InternalError("num !== numOfThings");
}
let { nodes, nodesToIndex } = serializeNodes(rootNodes);
let id = babelProgram.scope.generateDeclaredUidIdentifier();
babelProgram.node.body.unshift(
BabelTypes.variableDeclaration("var", [
BabelTypes.variableDeclarator(
id,
BabelTypes.callExpression(
BabelTypes.memberExpression(
BabelTypes.identifier("JSON"),
BabelTypes.identifier("parse")
),
[BabelTypes.stringLiteral(JSON.stringify(nodes))]
)
)
])
);
callbacks.forEach((cb, i) => {
cb(id.name, nodesToIndex.get(rootNodes[i])!);
});
}
14 changes: 13 additions & 1 deletion packages/macro/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export default createMacro(({ references, state, babel }: MacroArgs) => {
> = new Map();
let num = 0;

let programPath: NodePath<BabelTypes.Program>;

for (let exportName of [
"PropTypes",
"FunctionTypes",
Expand All @@ -65,6 +67,10 @@ export default createMacro(({ references, state, babel }: MacroArgs) => {
).name;

references[exportName].forEach(reference => {
if (!programPath) {
// @ts-ignore
programPath = reference.findParent(x => x.isProgram());
}
let { parentPath } = reference;

if (
Expand Down Expand Up @@ -107,7 +113,13 @@ export default createMacro(({ references, state, babel }: MacroArgs) => {
}
}
if (things.size) {
getTypes(state.filename, things, num);
getTypes(
state.filename,
things,
num,
// @ts-ignore
programPath
);
}
} catch (err) {
console.error(err);
Expand Down
Loading

0 comments on commit 6b9d0c2

Please sign in to comment.