This commit is contained in:
parent 2dccb8116a
commit 07b09f80f0
Signed by: jleicher
GPG Key ID: 98379ECFCFA57319
14 changed files with 348 additions and 33 deletions

View File

@ -1,8 +1,9 @@
{ {
"imports": { "imports": {
"ts_morph/": "https://deno.land/x/ts_morph/" "typescript": "npm:typescript@4.9.4",
"std/": "https://deno.land/std@0.178.0/"
}, },
"tasks": { "tasks": {
"dev": "deno run --watch main.ts" "dev": "deno run --allow-read --watch mod.ts"
} }
} }

35
denogen/deno.lock generated
View File

@ -16,6 +16,30 @@
"https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44", "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44",
"https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
"https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757", "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757",
"https://deno.land/std@0.178.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
"https://deno.land/std@0.178.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
"https://deno.land/std@0.178.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32",
"https://deno.land/std@0.178.0/fs/copy.ts": "14214efd94fc3aa6db1e4af2b4b9578e50f7362b7f3725d5a14ad259a5df26c8",
"https://deno.land/std@0.178.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688",
"https://deno.land/std@0.178.0/fs/ensure_dir.ts": "724209875497a6b4628dfb256116e5651c4f7816741368d6c44aab2531a1e603",
"https://deno.land/std@0.178.0/fs/ensure_file.ts": "c38602670bfaf259d86ca824a94e6cb9e5eb73757fefa4ebf43a90dd017d53d9",
"https://deno.land/std@0.178.0/fs/ensure_link.ts": "c0f5b2f0ec094ed52b9128eccb1ee23362a617457aa0f699b145d4883f5b2fb4",
"https://deno.land/std@0.178.0/fs/ensure_symlink.ts": "2955cc8332aeca9bdfefd05d8d3976b94e282b0f353392a71684808ed2ffdd41",
"https://deno.land/std@0.178.0/fs/eol.ts": "f1f2eb348a750c34500741987b21d65607f352cf7205f48f4319d417fff42842",
"https://deno.land/std@0.178.0/fs/exists.ts": "b8c8a457b71e9d7f29b9d2f87aad8dba2739cbe637e8926d6ba6e92567875f8e",
"https://deno.land/std@0.178.0/fs/expand_glob.ts": "45d17e89796a24bd6002e4354eda67b4301bb8ba67d2cac8453cdabccf1d9ab0",
"https://deno.land/std@0.178.0/fs/mod.ts": "bc3d0acd488cc7b42627044caf47d72019846d459279544e1934418955ba4898",
"https://deno.land/std@0.178.0/fs/move.ts": "4cb47f880e3f0582c55e71c9f8b1e5e8cfaacb5e84f7390781dd563b7298ec19",
"https://deno.land/std@0.178.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32",
"https://deno.land/std@0.178.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
"https://deno.land/std@0.178.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
"https://deno.land/std@0.178.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
"https://deno.land/std@0.178.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
"https://deno.land/std@0.178.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
"https://deno.land/std@0.178.0/path/mod.ts": "4b83694ac500d7d31b0cdafc927080a53dc0c3027eb2895790fb155082b0d232",
"https://deno.land/std@0.178.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
"https://deno.land/std@0.178.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
"https://deno.land/std@0.178.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
"https://deno.land/x/code_block_writer@11.0.3/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", "https://deno.land/x/code_block_writer@11.0.3/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5",
"https://deno.land/x/code_block_writer@11.0.3/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", "https://deno.land/x/code_block_writer@11.0.3/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff",
"https://deno.land/x/ts_morph@17.0.1/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", "https://deno.land/x/ts_morph@17.0.1/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9",
@ -30,5 +54,16 @@
"https://deno.land/x/ts_morph@17.0.1/mod.ts": "adba9b82f24865d15d2c78ef6074b9a7457011719056c9928c800f130a617c93", "https://deno.land/x/ts_morph@17.0.1/mod.ts": "adba9b82f24865d15d2c78ef6074b9a7457011719056c9928c800f130a617c93",
"https://deno.land/x/ts_morph@17.0.1/ts_morph.d.ts": "a54b0c51b06d84defedf5fdd59c773d803808ae7c9678f7165f7a1a6dfa7f6a3", "https://deno.land/x/ts_morph@17.0.1/ts_morph.d.ts": "a54b0c51b06d84defedf5fdd59c773d803808ae7c9678f7165f7a1a6dfa7f6a3",
"https://deno.land/x/ts_morph@17.0.1/ts_morph.js": "1bb80284b9e31a4c5c2078cd533fe9b12b4b2d710267055cb655225aa88fb2df" "https://deno.land/x/ts_morph@17.0.1/ts_morph.js": "1bb80284b9e31a4c5c2078cd533fe9b12b4b2d710267055cb655225aa88fb2df"
},
"npm": {
"specifiers": {
"typescript@4.9.4": "typescript@4.9.4"
},
"packages": {
"typescript@4.9.4": {
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
"dependencies": {}
}
}
} }
} }

View File

@ -1,23 +1,5 @@
import { Project, ResolutionHosts, ts } from "ts_morph/mod.ts"; import { Parser } from "./parsing/mod.ts";
export function generate(path: string) {
const project = new Project({
resolutionHost: ResolutionHosts.deno,
});
const file = project.addSourceFileAtPath(path);
const classes = file.getClasses();
const props = classes[0].getConstructors();
const ps = props[0].getParameters()[0];
console.log(classes[0].getProperties()[0].hasQuestionToken());
console.log(ps.isParameterProperty());
}
if (import.meta.main) { if (import.meta.main) {
generate("../myapp/todo.ts"); console.log(await new Parser().parse("../myapp/**/*.ts"));
} }

1
denogen/parsing/mod.ts Normal file
View File

@ -0,0 +1 @@
export * from "./parser.ts";

View File

@ -0,0 +1,109 @@
import ts from "typescript";
import { TypeRegistry } from "../registry.ts";
import { ParameterNode } from "./function.ts";
import { TypeReference } from "./reference.ts";
export class ClassNode {
private constructor(
public readonly name: string,
public readonly fields: FieldNode[],
public readonly methods: MethodNode[]
) {}
public static create(
registry: TypeRegistry,
node: ts.ClassDeclaration,
symbol?: ts.Symbol
) {
const fields: FieldNode[] = [];
const methods: MethodNode[] = [];
symbol?.members?.forEach((value) => {
const decl = value.declarations?.[0];
if (!decl) {
return;
}
if (ts.isPropertyDeclaration(decl) || ts.isParameter(decl)) {
fields.push(FieldNode.create(registry, decl));
} else if (
ts.isConstructorDeclaration(decl) ||
ts.isMethodDeclaration(decl)
) {
methods.push(MethodNode.create(registry, decl));
}
});
return new ClassNode(
symbol?.escapedName ?? node?.name?.escapedText ?? "",
fields,
methods
);
}
}
export type MemberVisibility = "public" | "private" | "protected";
/** Retrieve the visibility of a member based on the modifier flags */
function visibilityFrom(decl: ts.Declaration): MemberVisibility {
const modifiers = ts.getCombinedModifierFlags(decl);
if ((ts.ModifierFlags.Private & modifiers) === ts.ModifierFlags.Private) {
return "private";
}
if ((ts.ModifierFlags.Protected & modifiers) === ts.ModifierFlags.Protected) {
return "protected";
}
return "public";
}
export class FieldNode {
private constructor(
public readonly name: string,
public readonly type: TypeReference,
public readonly visibility: MemberVisibility,
public readonly optional: boolean
) {}
public static create(
registry: TypeRegistry,
decl: ts.PropertyDeclaration | ts.ParameterDeclaration
) {
return new FieldNode(
decl.name.getText(),
registry.getOrCreate(decl.type)!,
visibilityFrom(decl),
!!decl.questionToken // FIXME: or union with undefined
);
}
}
export class MethodNode {
static readonly ctorName = "__constructor";
public readonly isConstructor: boolean;
private constructor(
public readonly name: string,
public readonly visibility: MemberVisibility,
public readonly parameters: ParameterNode[],
public readonly returnType?: TypeReference
) {
this.isConstructor = name === MethodNode.ctorName;
}
public static create(
registry: TypeRegistry,
decl: ts.MethodDeclaration | ts.ConstructorDeclaration
) {
return new MethodNode(
decl?.name?.getText() ?? MethodNode.ctorName,
visibilityFrom(decl),
[],
registry.getOrCreate(decl.type)
);
}
}

View File

@ -0,0 +1,31 @@
import ts from "typescript";
import { TypeRegistry } from "../registry.ts";
import { TypeReference } from "./reference.ts";
export class FunctionNode {
private constructor(
public readonly name: string,
public readonly parameters: ParameterNode[],
public readonly returnType?: TypeReference
) {}
public static create(
registry: TypeRegistry,
node: ts.FunctionDeclaration,
symbol?: ts.Symbol
) {
return new FunctionNode(
symbol?.escapedName ?? node?.name?.escapedText ?? "",
[],
registry.getOrCreate(node.type)
);
}
}
export class ParameterNode {
constructor(
public readonly name: string,
public readonly type: TypeReference,
public readonly optional: boolean
) {}
}

View File

@ -0,0 +1,7 @@
import { ClassNode } from "./class.ts";
import { FunctionNode } from "./function.ts";
export { ClassNode } from "./class.ts";
export { FunctionNode } from "./function.ts";
export type Node = FunctionNode | ClassNode;

View File

@ -0,0 +1,3 @@
export class TypeReference {
constructor(public readonly name: string) {}
}

84
denogen/parsing/parser.ts Normal file
View File

@ -0,0 +1,84 @@
import ts from "typescript";
import { expandGlob, WalkEntry } from "std/fs/mod.ts";
import { ClassNode, FunctionNode, Node } from "./nodes/mod.ts";
import { TypeRegistry } from "./registry.ts";
/**
* Parser to retrieve a friendlier structure of the AST from the typescript compiler API
* to work with.
*
* Reference documentation: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker
*/
export class Parser {
private readonly _registry = new TypeRegistry();
/**
* Parse file matching the given glob pattern.
*/
public async parse(pattern: string): Promise<Node[]> {
const files = await filesMatching(pattern);
return this.parseSourceFiles(files);
}
/**
* Parse all source files using the typescript compiler API and build our nodes.
*/
private parseSourceFiles(files: WalkEntry[]): Node[] {
const program = ts.createProgram(
files.map((f) => f.path),
{}
);
const typeChecker = program.getTypeChecker();
return program.getSourceFiles().reduce<Node[]>((nodes, file) => {
// That's not interesting for us.
if (file.isDeclarationFile) {
return nodes;
}
// file.locals is undocumented but may contain all we need...
file.forEachChild((node) => {
if (ts.isFunctionDeclaration(node)) {
nodes.push(
FunctionNode.create(
this._registry,
node,
// @ts-expect-error Wrong getSymbolAtLocation types
typeChecker.getSymbolAtLocation(node?.name)
)
);
} else if (ts.isClassDeclaration(node)) {
nodes.push(
ClassNode.create(
this._registry,
node,
// @ts-expect-error Wrong getSymbolAtLocation types
typeChecker.getSymbolAtLocation(node?.name)
)
);
}
});
return nodes;
}, []);
}
}
/**
* Retrieve all files matching the given glob pattern.
*/
async function filesMatching(pattern: string): Promise<WalkEntry[]> {
const files: WalkEntry[] = [];
// Let's find all files
for await (const entry of expandGlob(pattern)) {
if (!entry.isFile) {
continue;
}
files.push(entry);
}
return files;
}

View File

@ -0,0 +1,37 @@
import ts from "typescript";
import { TypeReference } from "./nodes/reference.ts";
/**
* Retrieve and manage types referenced when parsing.
*/
export class TypeRegistry {
private readonly _types: Record<string, TypeReference> = {};
public getOrCreate(node?: ts.TypeNode): TypeReference | undefined {
if (!node) {
return undefined;
}
switch (node.kind) {
case ts.SyntaxKind.TypeReference:
{
const a = node as ts.TypeReferenceNode;
console.log(a.typeName.getText());
}
break;
case ts.SyntaxKind.ArrayType:
{
const b = node as ts.ArrayTypeNode;
console.log("array", b.elementType?.getText());
}
break;
case ts.SyntaxKind.StringKeyword:
console.log("string!");
break;
}
return undefined;
}
}

12
myapp/deno.lock generated Normal file
View File

@ -0,0 +1,12 @@
{
"version": "2",
"remote": {
"https://deno.land/std@0.178.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
"https://deno.land/std@0.178.0/bytes/concat.ts": "d26d6f3d7922e6d663dacfcd357563b7bf4a380ce5b9c2bbe0c8586662f25ce2",
"https://deno.land/std@0.178.0/uuid/_common.ts": "cb1441f4df460571fc0919e1c5c217f3e7006189b703caf946604b3f791ae34d",
"https://deno.land/std@0.178.0/uuid/mod.ts": "cd4da71beaa7ebfaa575ecb42e24e792559b65d8927e612c13b6ac5d1306f8bb",
"https://deno.land/std@0.178.0/uuid/v1.ts": "fe36009afce7ced96e1b5928565e12c5a8eb0df1a2b5063c0a72bda6b75c0de5",
"https://deno.land/std@0.178.0/uuid/v4.ts": "0f081880c156fd59b9e44e2f84ea0f94a3627e89c224eaf6cc982b53d849f37e",
"https://deno.land/std@0.178.0/uuid/v5.ts": "10558a9c09a06b86fef9e61205180b9585ec4fe3fed7d696e675b8e118f74e8e"
}
}

View File

@ -1,5 +1,5 @@
import { generate } from "denogen/mod.ts"; import { parse } from "denogen/mod.ts";
if (import.meta.main) { if (import.meta.main) {
generate("todo.ts"); parse(".");
} }

1
myapp/src/user.ts Normal file
View File

@ -0,0 +1 @@
export class User {}

View File

@ -1,17 +1,29 @@
export class Todo { export class Todo {
something?: boolean; public readonly id: string;
protected content: string;
constructor(private readonly id: string, private content: string) {} public constructor(content: string, private completed?: boolean) {
this.id = crypto.randomUUID();
// Update the todo's content.
setContent(content: string) {
this.content = content; this.content = content;
} }
// public markAsCompleted() {
// this.completed = true;
// }
// protected setContent(content: string) {
// this.content = content;
// }
} }
/** const todos: Todo[] = [];
* @post /api/todos
*/
export function createTodo(content: string): Todo { export function createTodo(content: string): Todo {
return new Todo(new Date().toISOString(), content); const newTodo = new Todo(content);
todos.push(newTodo);
return newTodo;
}
export function getTodos(): Todo[] {
return todos;
} }