Skip to content

Commit

Permalink
#36, convert type node: string literals.
Browse files Browse the repository at this point in the history
  • Loading branch information
ajvincent committed Jan 9, 2024
1 parent a7c7285 commit 490d785
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 6 deletions.
4 changes: 2 additions & 2 deletions stage_1_integration/bootstrap/buildTypesForStructures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,12 @@ function runConversion(
consoleTrap: TypeNodeToTypeStructureConsole,
subStructureResolver: SubstructureResolver,
converter: TypeNodeToTypeStructure,
callback: (typeStructure: TypeStructures) => void
callback: (typeStructure: string | TypeStructures) => void
): void
{
if (!typeNode)
return;
const typeStructure: TypeStructures | null = converter(
const typeStructure: string | TypeStructures | null = converter(
typeNode,
consoleTrap,
subStructureResolver
Expand Down
73 changes: 73 additions & 0 deletions stage_1_integration/bootstrap/convertTypeNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
Node,
SyntaxKind,
TypeNode,
} from "ts-morph";

import type {
TypeStructures,
} from "../snapshot/source/exports.js";

import type {
SubstructureResolver,
TypeNodeToTypeStructure,
TypeNodeToTypeStructureConsole
} from "./types/conversions.js";

const LiteralKeywords: ReadonlyMap<SyntaxKind, string> = new Map([
[SyntaxKind.AnyKeyword, "any"],
[SyntaxKind.BooleanKeyword, "boolean"],
[SyntaxKind.FalseKeyword, "false"],
[SyntaxKind.NeverKeyword, "never"],
[SyntaxKind.NumberKeyword, "number"],
[SyntaxKind.NullKeyword, "null"],
[SyntaxKind.ObjectKeyword, "object"],
[SyntaxKind.StringKeyword, "string"],
[SyntaxKind.SymbolKeyword, "symbol"],
[SyntaxKind.TrueKeyword, "true"],
[SyntaxKind.UndefinedKeyword, "undefined"],
[SyntaxKind.UnknownKeyword, "unknown"],
[SyntaxKind.VoidKeyword, "void"],
]);

export default function convertTypeNode(
typeNode: TypeNode,
consoleTrap: TypeNodeToTypeStructureConsole,
subStructureResolver: SubstructureResolver
): string | TypeStructures | null
{

if (Node.isLiteralTypeNode(typeNode)) {
typeNode = typeNode.getFirstChildOrThrow();
}
const kind: SyntaxKind = typeNode.getKind();

const keyword = LiteralKeywords.get(kind);
if (keyword) {
return keyword;
}

reportConversionFailure(
"unsupported type node", typeNode, typeNode, consoleTrap
);
void(subStructureResolver);
return null;
}
convertTypeNode satisfies TypeNodeToTypeStructure;

function reportConversionFailure(
prefixMessage: string,
failingNode: Node,
failingTypeNode: TypeNode,
conversionFailCallback: TypeNodeToTypeStructureConsole,
): null
{
const pos = failingNode.getPos();
const { line, column } = failingNode.getSourceFile().getLineAndColumnAtPos(pos);

conversionFailCallback(
`${prefixMessage}: "${failingNode.getKindName()}" at line ${line}, column ${column}`,
failingTypeNode
);
return null;
}
2 changes: 1 addition & 1 deletion stage_1_integration/bootstrap/types/conversions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ export type TypeNodeToTypeStructure = (
typeNode: TypeNode,
_console: TypeNodeToTypeStructureConsole,
subStructureResolver: SubstructureResolver,
) => TypeStructures | null;
) => string | TypeStructures | null;
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,12 @@ function runConversion(
consoleTrap: TypeNodeToTypeStructureConsole,
subStructureResolver: SubstructureResolver,
converter: TypeNodeToTypeStructure,
callback: (typeStructure: TypeStructures) => void
callback: (typeStructure: string | TypeStructures) => void
): void
{
if (!typeNode)
return;
const typeStructure: TypeStructures | null = converter(
const typeStructure: string | TypeStructures | null = converter(
typeNode,
consoleTrap,
subStructureResolver
Expand Down
73 changes: 73 additions & 0 deletions stage_2_fullset/snapshot/source/bootstrap/convertTypeNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
Node,
SyntaxKind,
TypeNode,
} from "ts-morph";

import type {
TypeStructures,
} from "../exports.js";

import type {
SubstructureResolver,
TypeNodeToTypeStructure,
TypeNodeToTypeStructureConsole
} from "./types/conversions.js";

const LiteralKeywords: ReadonlyMap<SyntaxKind, string> = new Map([
[SyntaxKind.AnyKeyword, "any"],
[SyntaxKind.BooleanKeyword, "boolean"],
[SyntaxKind.FalseKeyword, "false"],
[SyntaxKind.NeverKeyword, "never"],
[SyntaxKind.NumberKeyword, "number"],
[SyntaxKind.NullKeyword, "null"],
[SyntaxKind.ObjectKeyword, "object"],
[SyntaxKind.StringKeyword, "string"],
[SyntaxKind.SymbolKeyword, "symbol"],
[SyntaxKind.TrueKeyword, "true"],
[SyntaxKind.UndefinedKeyword, "undefined"],
[SyntaxKind.UnknownKeyword, "unknown"],
[SyntaxKind.VoidKeyword, "void"],
]);

export default function convertTypeNode(
typeNode: TypeNode,
consoleTrap: TypeNodeToTypeStructureConsole,
subStructureResolver: SubstructureResolver
): string | TypeStructures | null
{

if (Node.isLiteralTypeNode(typeNode)) {
typeNode = typeNode.getFirstChildOrThrow();
}
const kind: SyntaxKind = typeNode.getKind();

const keyword = LiteralKeywords.get(kind);
if (keyword) {
return keyword;
}

reportConversionFailure(
"unsupported type node", typeNode, typeNode, consoleTrap
);
void(subStructureResolver);
return null;
}
convertTypeNode satisfies TypeNodeToTypeStructure;

function reportConversionFailure(
prefixMessage: string,
failingNode: Node,
failingTypeNode: TypeNode,
conversionFailCallback: TypeNodeToTypeStructureConsole,
): null
{
const pos = failingNode.getPos();
const { line, column } = failingNode.getSourceFile().getLineAndColumnAtPos(pos);

conversionFailCallback(
`${prefixMessage}: "${failingNode.getKindName()}" at line ${line}, column ${column}`,
failingTypeNode
);
return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ export type TypeNodeToTypeStructure = (
typeNode: TypeNode,
_console: TypeNodeToTypeStructureConsole,
subStructureResolver: SubstructureResolver,
) => TypeStructures | null;
) => string | TypeStructures | null;
110 changes: 110 additions & 0 deletions stage_2_fullset/spec-snapshot/source/bootstrap/convertTypeNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
ModuleKind,
ModuleResolutionKind,
Project,
ProjectOptions,
ScriptTarget,
TypeNode,
VariableDeclaration,
} from "ts-morph";

import {
TypeStructures
} from "#stage_two/snapshot/source/exports.js";

import {
StructuresClassesMap
} from "#stage_two/snapshot/source/internal-exports.js";

import convertTypeNode from "#stage_two/snapshot/source/bootstrap/convertTypeNode.js";

import type {
TypeNodeToTypeStructureConsole
} from "#stage_two/snapshot/source/bootstrap/types/conversions.js";

describe("convertTypeNode generates correct type structures, with type", () => {
let declaration: VariableDeclaration;
let structure: string | TypeStructures | null;

let failMessage: string | undefined;
let failNode: TypeNode | null;
function failCallback(message: string, typeNode: TypeNode): void {
failMessage = message;
failNode = typeNode;
}
failCallback satisfies TypeNodeToTypeStructureConsole;

beforeAll(() => {
failMessage = undefined;
failNode = null;

const TSC_CONFIG: ProjectOptions = {
"compilerOptions": {
"lib": ["es2022"],
"module": ModuleKind.ESNext,
"target": ScriptTarget.ESNext,
"moduleResolution": ModuleResolutionKind.NodeNext,
},
skipAddingFilesFromTsConfig: true,
useInMemoryFileSystem: true,
};

const project = new Project(TSC_CONFIG);
const sourceFile = project.createSourceFile("file.ts", `
const refSymbol = Symbol("reference symbol");
enum NumberEnum {
one = 1,
two,
three,
}
const A: string;
`.trim() + "\n");
declaration = sourceFile.getVariableDeclarationOrThrow("A");
});
beforeEach(() => {
failMessage = undefined;
failNode = null;
});

afterEach(() => structure = null);

function setTypeStructure(
rawType: string,
console: TypeNodeToTypeStructureConsole
): void
{
declaration.setType(rawType);
const typeNode = declaration.getTypeNodeOrThrow();
structure = convertTypeNode(
typeNode,
console,
node => StructuresClassesMap.clone(node.getStructure())
);
}

describe("string literal", () => {
function stringSpec(s: string): void {
it(s, () => {
setTypeStructure(s, failCallback);
expect(structure).toBe(s);
expect(failMessage).toBe(undefined);
expect(failNode).toBe(null);
});
}

stringSpec("any");
stringSpec("boolean");
stringSpec("false");
stringSpec("never");
stringSpec("number");
stringSpec("null");
stringSpec("object");
stringSpec("string");
stringSpec("symbol");
stringSpec("true");
stringSpec("undefined");
stringSpec("unknown");
stringSpec("void");
});
});

0 comments on commit 490d785

Please sign in to comment.