Skip to content

Commit

Permalink
#94, structure-to-node map using adjustForOverloads module
Browse files Browse the repository at this point in the history
  • Loading branch information
ajvincent committed Jul 6, 2024
1 parent 430b992 commit 9c1cb72
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 111 deletions.
82 changes: 55 additions & 27 deletions dist/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,33 @@ function prependOverload(parentStructure, overloadStructure, kind) {
parentStructure.overloads.unshift(overload);
return overload;
}
//#endregion move structure overloads inside their parent structures
function getOverloadIndex(node) {
const kind = node.getKind();
let matchingNodes = node
.getParentOrThrow()
.getChildrenOfKind(kind);
switch (kind) {
case SyntaxKind.Constructor:
matchingNodes = matchingNodes.slice();
break;
case SyntaxKind.FunctionDeclaration: {
const name = node.getName();
if (name === undefined)
return -1;
matchingNodes = matchingNodes.filter((n) => n.getName() === name);
break;
}
case SyntaxKind.MethodDeclaration: {
const name = node.getName();
const isStatic = node.isStatic();
matchingNodes = matchingNodes.filter((n) => n.isStatic() === isStatic && n.getName() === name);
break;
}
}
matchingNodes.pop();
return matchingNodes.indexOf(node);
}

var _a$6;
// #endregion preamble
Expand Down Expand Up @@ -1242,21 +1269,6 @@ function structureToNodeMap(nodeWithStructures, useTypeAwareStructures, hashNeed
*/
class StructureAndNodeData {
static #knownSyntaxKinds;
//FIXME: Move this to its own testable module file, and write tests explicitly for this! You've been burned a lot by overload instability.
static #isOverload(node) {
if (Node.isAmbientable(node) && node.hasDeclareKeyword())
return false;
if (Node.isMethodDeclaration(node) || Node.isConstructorDeclaration(node)) {
const parent = node.getParentOrThrow();
if (Node.isAmbientable(parent) && parent.hasDeclareKeyword())
return false;
}
const nodes = node.getOverloads();
const implNode = node.getImplementation();
if (implNode)
nodes.push(implNode);
return nodes[nodes.length - 1] !== node;
}
structureToNodeMap = new Map();
// #region private fields, and life-cycle.
#rootNode;
Expand Down Expand Up @@ -1363,7 +1375,7 @@ class StructureAndNodeData {
* @returns the hash part for this node.
*
* @remarks
* The current format is `${node.getKindName}:${node.getName()}(/overload)?`
* The current format is `${node.getKindName}:${node.getName()}(/overload:1)?`
*/
#hashNodeLocal(node) {
let hash = this.#nodeToHash.get(node) ?? "";
Expand Down Expand Up @@ -1397,14 +1409,20 @@ class StructureAndNodeData {
* node.isOverload() lies to us for type definition files.
*/
if (hash && Node.isOverloadable(node)) {
const isOverload = _a$6.#isOverload(node);
if (isOverload) {
hash += "/overload";
let overloadIndex = NaN;
if (Node.isConstructorDeclaration(node) ||
Node.isMethodDeclaration(node) ||
Node.isFunctionDeclaration(node)) {
overloadIndex = getOverloadIndex(node);
}
else {
assert(false, "what kind of node is this? " +
node.getStartLineNumber() +
":" +
node.getStartLinePos());
}
const filePath = node.getSourceFile().getFilePath();
if (filePath.match(/Overload/)) {
const parentHash = this.#nodeToHash.get(node.getParentOrThrow());
console.log(filePath + ":" + node.getStartLineNumber(), parentHash + "/" + hash);
if (overloadIndex > -1) {
hash += "/overload:" + overloadIndex;
}
}
}
Expand Down Expand Up @@ -1505,9 +1523,13 @@ class StructureAndNodeData {
3. The node hash from the structure is wrong. `#createNodeHashFromStructure()`.
*/
let parentMsg = "";
const sourceFile = this.#rootNode.getSourceFile();
if (parentNode) {
const sourceFile = this.#rootNode.getSourceFile();
parentMsg = `, parent at ${JSON.stringify(sourceFile.getLineAndColumnAtPos(parentNode.getPos()))}`;
const { line, column } = sourceFile.getLineAndColumnAtPos(parentNode.getPos());
parentMsg = `, parent at ${sourceFile.getFilePath()} line ${line} column ${column}`;
}
else {
parentMsg = `, at ${sourceFile.getFilePath()}`;
}
assert(false, `Expected candidate node to exist, structureHash = "${structureHash}", nodeHash = "${nodeHash}"${parentMsg}`);
}
Expand All @@ -1529,8 +1551,9 @@ class StructureAndNodeData {
*/
#createNodeHashFromStructure(structure) {
let parentHash = "";
let parentStructure;
if (structure !== this.#rootStructure) {
const parentStructure = this.#structureToParent.get(structure);
parentStructure = this.#structureToParent.get(structure);
const parentNode = this.structureToNodeMap.get(parentStructure);
const parentHashTemp = this.#nodeToHash.get(parentNode);
assert(parentHashTemp !== undefined, "must have a parent hash");
Expand All @@ -1543,7 +1566,12 @@ class StructureAndNodeData {
if (localKind === "FirstStatement")
localKind = "VariableStatement";
if (StructureKind[structure.kind].endsWith("Overload")) {
localKind = "overload";
assert(parentStructure &&
"overloads" in parentStructure &&
Array.isArray(parentStructure.overloads), "must find the overload index in the parent structure");
localKind =
"overload:" +
parentStructure.overloads.indexOf(structure);
}
let hash = parentHash + "/" + localKind;
if ("name" in structure)
Expand Down
4 changes: 4 additions & 0 deletions stage_2_integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ First I have to match [ts-morph structures to ts-morph nodes](./source/bootstrap

There are nuances to both node trees and structure trees which make this a specialized task. There is a fair bit of trial-and-error in developing this. The module tracks debugging information (structure hashes, parent hashes of structure and node, node-to-hash maps, etc.), for the occasions when it fails.

Failures here are fatal exceptions. The structure-to-node map code _must_ work flawlessly. (Which is a high bar, I know.) Unlike the type structures, these structures directly reflect the ts-morph nodes they come from - and in theory, we can pass any of them back to ts-morph. Any failures in building the Map destabilize the rest of the code.

For this reason, we have to assert that for every `Structure`, we can refer back to a `Node` where it came from. It's an internal assertion, but absolutely critical.

### Finding the type nodes for a given node

Next we need to [find where the type nodes are](./source/bootstrap/buildTypesForStructures.ts) for each structure we care about. This takes a `Map<Structures, Node>`, and a special type node converter (which I describe in the next section) and for each structure-node pair, runs the following algorithm:
Expand Down
61 changes: 21 additions & 40 deletions stage_2_integration/source/bootstrap/structureToNodeMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import assert from "node:assert/strict";

import {
type ClassDeclaration,
type ClassExpression,
type FunctionDeclarationOverloadStructure,
type JSDocableNode,
type JSDoc,
type Structures,
Node,
type ObjectLiteralExpression,
type OverloadableNode,
StructureKind,
SyntaxKind,
forEachStructureChild,
Expand All @@ -31,6 +28,7 @@ import type {

import {
fixFunctionOverloads,
getOverloadIndex,
} from "./adjustForOverloads.js";
// #endregion preamble

Expand Down Expand Up @@ -92,27 +90,6 @@ class StructureAndNodeData
{
static #knownSyntaxKinds?: ReadonlySet<SyntaxKind>;

//FIXME: Move this to its own testable module file, and write tests explicitly for this! You've been burned a lot by overload instability.
static #isOverload(
node: OverloadableNode & Node
): boolean
{
if (Node.isAmbientable(node) && node.hasDeclareKeyword())
return false;

if (Node.isMethodDeclaration(node) || Node.isConstructorDeclaration(node)) {
const parent: ClassDeclaration | ClassExpression | ObjectLiteralExpression = node.getParentOrThrow();
if (Node.isAmbientable(parent) && parent.hasDeclareKeyword())
return false;
}

const nodes: OverloadableNode[] = node.getOverloads();
const implNode = node.getImplementation();
if (implNode)
nodes.push(implNode);
return nodes[nodes.length - 1] !== node;
}

readonly structureToNodeMap = new Map<Structures, Node>;

// #region private fields, and life-cycle.
Expand Down Expand Up @@ -254,7 +231,7 @@ class StructureAndNodeData
* @returns the hash part for this node.
*
* @remarks
* The current format is `${node.getKindName}:${node.getName()}(/overload)?`
* The current format is `${node.getKindName}:${node.getName()}(/overload:1)?`
*/
#hashNodeLocal(
node: Node
Expand Down Expand Up @@ -296,15 +273,14 @@ class StructureAndNodeData
* node.isOverload() lies to us for type definition files.
*/
if (hash && Node.isOverloadable(node)) {
const isOverload = StructureAndNodeData.#isOverload(node);
if (isOverload) {
hash += "/overload";
let overloadIndex = NaN;
if (Node.isConstructorDeclaration(node) || Node.isMethodDeclaration(node) || Node.isFunctionDeclaration(node)) {
overloadIndex = getOverloadIndex(node);
} else {
assert(false, "what kind of node is this? " + node.getStartLineNumber() + ":" + node.getStartLinePos());
}

const filePath = node.getSourceFile().getFilePath();
if (filePath.match(/Overload/)) {
const parentHash = this.#nodeToHash.get(node.getParentOrThrow());
console.log(filePath + ":" + node.getStartLineNumber(), parentHash + "/" + hash);
if (overloadIndex > -1) {
hash += "/overload:" + overloadIndex;
}
}
}
Expand Down Expand Up @@ -450,12 +426,12 @@ class StructureAndNodeData
*/

let parentMsg = "";
const sourceFile = this.#rootNode!.getSourceFile();
if (parentNode) {
const sourceFile = this.#rootNode!.getSourceFile();

parentMsg = `, parent at ${
JSON.stringify(sourceFile.getLineAndColumnAtPos(parentNode.getPos()))
}`;
const { line, column } = sourceFile.getLineAndColumnAtPos(parentNode.getPos());
parentMsg = `, parent at ${sourceFile.getFilePath()} line ${line} column ${column}`;
} else {
parentMsg = `, at ${sourceFile.getFilePath()}`;
}
assert(false,
`Expected candidate node to exist, structureHash = "${
Expand Down Expand Up @@ -488,8 +464,9 @@ class StructureAndNodeData
): string
{
let parentHash = "";
let parentStructure: Structures | undefined;
if (structure !== this.#rootStructure) {
const parentStructure = this.#structureToParent.get(structure)!;
parentStructure = this.#structureToParent.get(structure)!;
const parentNode = this.structureToNodeMap.get(parentStructure)!;
const parentHashTemp = this.#nodeToHash.get(parentNode);
assert(parentHashTemp !== undefined, "must have a parent hash");
Expand All @@ -504,7 +481,11 @@ class StructureAndNodeData
localKind = "VariableStatement";

if (StructureKind[structure.kind].endsWith("Overload")) {
localKind = "overload";
assert(
parentStructure && "overloads" in parentStructure && Array.isArray(parentStructure.overloads),
"must find the overload index in the parent structure"
);
localKind = "overload:" + (parentStructure.overloads as Structures[]).indexOf(structure);
}

let hash: string = parentHash + "/" + localKind;
Expand Down
2 changes: 1 addition & 1 deletion stage_2_snapshot/buildStage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ BPSet.markReady();
BPSet.main.addSubtarget("copySnapshot");
BPSet.main.addSubtarget("test");
// at the end to allow for debugging before this
// BPSet.main.addSubtarget("eslint");
BPSet.main.addSubtarget("eslint");
}
await BPSet.main.run();
export default Promise.resolve();
Loading

0 comments on commit 9c1cb72

Please sign in to comment.