wip
This commit is contained in:
parent
2dccb8116a
commit
07b09f80f0
@ -1,8 +1,9 @@
|
||||
{
|
||||
"imports": {
|
||||
"ts_morph/": "https://deno.land/x/ts_morph/"
|
||||
"typescript": "npm:typescript@4.9.4",
|
||||
"std/": "https://deno.land/std@0.178.0/"
|
||||
},
|
||||
"tasks": {
|
||||
"dev": "deno run --watch main.ts"
|
||||
"dev": "deno run --allow-read --watch mod.ts"
|
||||
}
|
||||
}
|
||||
|
||||
35
denogen/deno.lock
generated
35
denogen/deno.lock
generated
@ -16,6 +16,30 @@
|
||||
"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/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/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff",
|
||||
"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/ts_morph.d.ts": "a54b0c51b06d84defedf5fdd59c773d803808ae7c9678f7165f7a1a6dfa7f6a3",
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +1,5 @@
|
||||
import { Project, ResolutionHosts, ts } from "ts_morph/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());
|
||||
}
|
||||
import { Parser } from "./parsing/mod.ts";
|
||||
|
||||
if (import.meta.main) {
|
||||
generate("../myapp/todo.ts");
|
||||
console.log(await new Parser().parse("../myapp/**/*.ts"));
|
||||
}
|
||||
|
||||
1
denogen/parsing/mod.ts
Normal file
1
denogen/parsing/mod.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./parser.ts";
|
||||
109
denogen/parsing/nodes/class.ts
Normal file
109
denogen/parsing/nodes/class.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
31
denogen/parsing/nodes/function.ts
Normal file
31
denogen/parsing/nodes/function.ts
Normal 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
|
||||
) {}
|
||||
}
|
||||
7
denogen/parsing/nodes/mod.ts
Normal file
7
denogen/parsing/nodes/mod.ts
Normal 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;
|
||||
3
denogen/parsing/nodes/reference.ts
Normal file
3
denogen/parsing/nodes/reference.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class TypeReference {
|
||||
constructor(public readonly name: string) {}
|
||||
}
|
||||
84
denogen/parsing/parser.ts
Normal file
84
denogen/parsing/parser.ts
Normal 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;
|
||||
}
|
||||
37
denogen/parsing/registry.ts
Normal file
37
denogen/parsing/registry.ts
Normal 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
12
myapp/deno.lock
generated
Normal 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"
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { generate } from "denogen/mod.ts";
|
||||
import { parse } from "denogen/mod.ts";
|
||||
|
||||
if (import.meta.main) {
|
||||
generate("todo.ts");
|
||||
parse(".");
|
||||
}
|
||||
|
||||
1
myapp/src/user.ts
Normal file
1
myapp/src/user.ts
Normal file
@ -0,0 +1 @@
|
||||
export class User {}
|
||||
@ -1,17 +1,29 @@
|
||||
export class Todo {
|
||||
something?: boolean;
|
||||
public readonly id: string;
|
||||
protected content: string;
|
||||
|
||||
constructor(private readonly id: string, private content: string) {}
|
||||
|
||||
// Update the todo's content.
|
||||
setContent(content: string) {
|
||||
public constructor(content: string, private completed?: boolean) {
|
||||
this.id = crypto.randomUUID();
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
// public markAsCompleted() {
|
||||
// this.completed = true;
|
||||
// }
|
||||
|
||||
// protected setContent(content: string) {
|
||||
// this.content = content;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* @post /api/todos
|
||||
*/
|
||||
const todos: 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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user