-
-
Notifications
You must be signed in to change notification settings - Fork 31
/
service.mts
102 lines (90 loc) · 3.43 KB
/
service.mts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import type { Project, SourceFile } from "ts-morph";
import ts from "typescript";
import type { FunctionDescription } from "./common.mjs";
import { serviceFileName } from "./constants.mjs";
export type Service = {
node: SourceFile;
methods: Array<FunctionDescription>;
};
export async function getServices(project: Project): Promise<Service> {
const node = project
.getSourceFiles()
.find((sourceFile) => sourceFile.getFilePath().includes(serviceFileName));
if (!node) {
throw new Error("No service node found");
}
const methods = getMethodsFromService(node);
return {
methods,
node,
} satisfies Service;
}
function getMethodsFromService(node: SourceFile): FunctionDescription[] {
const variableStatements = node.getVariableStatements();
// The first variable statement is `const client = createClient(createConfig())`, so we skip it
return variableStatements.splice(1).flatMap((variableStatement) => {
const declarations = variableStatement.getDeclarations();
return declarations.map((declaration) => {
if (!ts.isVariableDeclaration(declaration.compilerNode)) {
throw new Error("Variable declaration not found");
}
const initializer = declaration.getInitializer();
if (!initializer) {
throw new Error("Initializer not found");
}
if (!ts.isArrowFunction(initializer.compilerNode)) {
throw new Error("Arrow function not found");
}
const methodBlockNode = initializer.compilerNode.body;
if (!methodBlockNode || !ts.isBlock(methodBlockNode)) {
throw new Error("Method block not found");
}
const foundReturnStatement = methodBlockNode.statements.find(
(s) => s.kind === ts.SyntaxKind.ReturnStatement,
);
if (!foundReturnStatement) {
throw new Error("Return statement not found");
}
const returnStatement = foundReturnStatement as ts.ReturnStatement;
const foundCallExpression = returnStatement.expression;
if (!foundCallExpression) {
throw new Error("Call expression not found");
}
const callExpression = foundCallExpression as ts.CallExpression;
const propertyAccessExpression =
callExpression.expression as ts.PropertyAccessExpression;
const httpMethodName = propertyAccessExpression.name.getText();
if (!httpMethodName) {
throw new Error("httpMethodName not found");
}
const getAllChildren = (tsNode: ts.Node): Array<ts.Node> => {
const childItems = tsNode.getChildren(node.compilerNode);
if (childItems.length) {
const allChildren = childItems.map(getAllChildren);
return [tsNode].concat(allChildren.flat());
}
return [tsNode];
};
const children = getAllChildren(initializer.compilerNode);
// get all JSDoc comments
// this should be an array of 1 or 0
const jsDocs = children
.filter((c) => c.kind === ts.SyntaxKind.JSDoc)
.map((c) => c.getText(node.compilerNode));
// get the first JSDoc comment
const jsDoc = jsDocs?.[0];
const isDeprecated = children.some(
(c) => c.kind === ts.SyntaxKind.JSDocDeprecatedTag,
);
const methodDescription: FunctionDescription = {
node,
method: declaration,
methodBlock: methodBlockNode,
httpMethodName,
jsDoc,
isDeprecated,
} satisfies FunctionDescription;
return methodDescription;
});
});
}