1 module dast.gen.tsapi;
2 import dast.util,
3 std.traits,
4 tame.meta;
5 import std.meta : Filter, Seq = AliasSeq,
6 IndexOf = staticIndexOf,
7 SeqMap = staticMap;
8 
9 import std.traits : FuncTypeOf = FunctionTypeOf,
10 Params = Parameters,
11 ParamDefaults = ParameterDefaults,
12 ParamIds = ParameterIdentifierTuple;
13 
14 struct type { // @suppress(dscanner.style.phobos_naming_convention)
15 	string name;
16 }
17 
18 /// The summary of the function.
19 struct summary { // @suppress(dscanner.style.phobos_naming_convention)
20 	string content;
21 }
22 
23 private enum shouldInclude(alias x) = isCallable!x && !hasUDA!(x, ignore);
24 private enum hasName(alias x) = __traits(compiles, (string s) { s = x.name; });
25 template getName(alias x, string defName = "") {
26 	static if (hasName!x)
27 		enum getName = x.name;
28 	else
29 		enum getName = defName;
30 }
31 
32 enum isStr(alias x) = is(typeof(x) : const(char)[]);
33 
34 alias AllActions(alias attr, modules...) = Filter!(shouldInclude, getSymbolsWith!(attr, modules));
35 
36 template ForModules(modules...) {
37 	template allActions(alias attr) {
38 		alias allActions = Seq!();
39 		static foreach (f; AllActions!(attr, modules)) {
40 			allActions = Seq!(allActions, getUDAs!(f, attr));
41 		}
42 	}
43 
44 	void genAPIDef(alias attr, alias getType = TSTypeOf, R)(ref R sink) {
45 		foreach (f; AllActions!(attr, modules)) {
46 			foreach (attr; getUDAs!(f, attr)) {
47 				sink.put("/**\n");
48 				getDoc!(f, getType)(sink);
49 				sink.put(" */\n");
50 				sink.put(getName!(attr, __traits(identifier, f)));
51 				sink.put('(');
52 				getArgs!(f, getType)(sink);
53 				sink.put("): ");
54 				static if (hasUDA!(f, type))
55 					sink.put(getUDAs!(f, type)[0].name);
56 				else
57 					sink.put(getType!(ReturnType!f));
58 				sink.put('\n');
59 			}
60 		}
61 	}
62 
63 	void genTypeDef(alias getType = TSTypeOf, R)(ref R sink) {
64 		foreach (t; modules) {
65 			sink.put("export type ");
66 			sink.put(__traits(identifier, t));
67 			sink.put(" = {\n\t");
68 			foreach (i, alias f; t.tupleof) {
69 				alias loc = __traits(getLocation, f);
70 				const comment = getComment(loc[0], loc[1] - 1);
71 				if (comment) {
72 					sink.put("/**");
73 					sink.put(comment);
74 					sink.put(" */\n\t");
75 				}
76 				sink.put(__traits(identifier, f));
77 				sink.put(": ");
78 				static if (hasUDA!(f, type))
79 					sink.put(getUDAs!(f, type)[0].name);
80 				else
81 					sink.put(getType!(typeof(f)));
82 				sink.put('\n');
83 				static if (i + 1 < t.tupleof.length)
84 					sink.put('\t');
85 			}
86 			sink.put("}\n");
87 		}
88 	}
89 
90 	void genEnum(R)(ref R sink) {
91 		import std.string,
92 		std.conv : text;
93 
94 		foreach (m; modules) {
95 			foreach (name; __traits(allMembers, m)) {
96 				alias Enum = __traits(getMember, m, name);
97 				static if (is(Enum == enum)) {
98 					sink.put("export type ");
99 					sink.put(Enum.stringof);
100 					sink.put(" = ");
101 					{
102 						alias toOriginal(alias x) = Seq!(cast(OriginalType!Enum)x, " | ");
103 						sink.put(text(SeqMap!(toOriginal, EnumMembers!Enum)[0 .. $ - 1]));
104 					}
105 					sink.put("\nexport const ");
106 					sink.put(Enum.stringof);
107 					sink.put(": Record<string, string> = {\n");
108 					foreach (member; __traits(allMembers, Enum)) {
109 						sink.put("\t'");
110 						alias f = __traits(getMember, Enum, member);
111 						sink.put(text(cast(OriginalType!Enum)f));
112 						sink.put("': '");
113 						alias loc = __traits(getLocation, f);
114 						sink.put(getComment(loc[0], loc[1] - 1).strip());
115 						sink.put("',\n");
116 					}
117 					sink.put("}\n");
118 				}
119 			}
120 		}
121 	}
122 }
123 
124 unittest {
125 	import std.stdio;
126 	import std.array;
127 
128 	alias m = ForModules!(dast.gen.tsapi);
129 	pragma(msg, m.allActions!Action);
130 
131 	auto app = appender!string;
132 	m.genAPIDef!Action(app);
133 	writeln(app[]);
134 }
135 
136 void getDoc(alias f, alias getType = TSTypeOf, R)(ref R sink) {
137 	alias loc = __traits(getLocation, f);
138 	if (loc[1] > 1) {
139 		sink.put(" *");
140 		foreach (attr; getUDAs!(f, summary)) {
141 			sink.put(attr.content);
142 		}
143 		sink.put(getComment(loc[0], loc[1] - 1));
144 		sink.put('\n');
145 	}
146 	static if (is(FuncTypeOf!f P == __parameters)) {
147 		alias PIT = ParamIds!f;
148 		alias set = Filter!(isStr, __traits(getAttributes, f));
149 		static foreach (i, T; P) {
150 			{
151 				alias p = P[i .. i + 1];
152 				alias a = getParamUDAs!(type, f, __traits(getAttributes, p));
153 				static if (a.length) {
154 					enum typeName = a[0].name;
155 				} else {
156 					enum typeName = getType!p;
157 				}
158 				static if (typeName.length) {
159 					sink.put(" * @param ");
160 					sink.put(KeyName!(p, PIT[i].length ? PIT[i] : "arg" ~ i.stringof));
161 					sink.put(' ');
162 					static foreach (attr; __traits(getAttributes, p))
163 						static if (isStr!attr && IndexOf!(attr, set) == -1)
164 							sink.put(attr);
165 					sink.put('\n');
166 				}
167 			}
168 		}
169 		if (set.length)
170 			sink.put(" * @returns ");
171 		foreach (attr; set)
172 			static if (isStr!attr)
173 				sink.put(attr);
174 		if (set.length)
175 			sink.put('\n');
176 	} else
177 		static assert(0, f.stringof ~ " is not a function");
178 }
179 
180 template getParamUDAs(alias attr, alias f, attrs...) {
181 	alias fAttr = __traits(getAttributes, f);
182 	alias getParamUDAs = Seq!();
183 	static foreach (a; attrs) {
184 		static if (IndexOf!(a, fAttr) == -1) {
185 			static if (__traits(isSame, a, attr))
186 				getParamUDAs = Seq!(getParamUDAs, a);
187 			else static if (is(typeof(a)))
188 				static if (is(typeof(a) == attr))
189 					getParamUDAs = Seq!(getParamUDAs, a);
190 		}
191 	}
192 }
193 
194 void getArgs(alias f, alias getType = TSTypeOf, R)(ref R sink) {
195 	static if (is(typeof(f) P == __parameters)) {
196 		alias PIT = ParamIds!f;
197 		static foreach (i, T; P) {
198 			{
199 				alias p = P[i .. i + 1];
200 				alias a = getParamUDAs!(type, f, __traits(getAttributes, p));
201 				static if (a.length) {
202 					enum typeName = a[0].name;
203 				} else {
204 					enum typeName = getType!p;
205 				}
206 				static if (typeName.length) {
207 					sink.put(KeyName!(p, PIT[i].length ? PIT[i] : "arg" ~ i.stringof));
208 					static if (!is(ParamDefaults!f[i] == void))
209 						sink.put('?');
210 					sink.put(": ");
211 					sink.put(typeName);
212 					static if (i + 1 < P.length)
213 						sink.put(", ");
214 				}
215 			}
216 		}
217 	} else
218 		static assert(0, f.stringof ~ " is not a function");
219 }
220 
221 /// Get the type name of the given type, empty string if to omit.
222 template TSTypeOf(T) {
223 	static if (hasUDA!(T, type) && hasName!(getUDAs!(T, type)[0]))
224 		enum TSTypeOf = getUDAs!(T, type)[0].name;
225 	else static if (is(T == U[], U)) {
226 		static if (is(T : const(char)[]))
227 			enum TSTypeOf = "string";
228 		else
229 			enum TSTypeOf = TSTypeOf!U ~ "[]";
230 	} else static if (isNumeric!T)
231 		enum TSTypeOf = "number";
232 	else static if (isBoolean!T)
233 		enum TSTypeOf = "boolean";
234 	else static if (isSomeChar!T)
235 		enum TSTypeOf = "string";
236 	else static if (is(T == void))
237 		enum TSTypeOf = "void";
238 	else static if (is(T : typeof(null)))
239 		enum TSTypeOf = "null";
240 	else static if (is(T : Throwable))
241 		enum TSTypeOf = "Error";
242 	else
243 		enum TSTypeOf = "any";
244 }
245 
246 /// Get the comment at the given line and column.
247 char[] getComment(string filename, uint line, uint col = 1) {
248 	import std.stdio,
249 	std.string;
250 
251 	--line;
252 	--col;
253 	uint i;
254 	char[] result;
255 	try {
256 		foreach (l; File(filename).byLine) {
257 			++i;
258 			if ((i - line <= 1) && l.length >= col) {
259 				l = l[col .. $].stripLeft;
260 				if (l.startsWith("///")) {
261 					if (result.length)
262 						result ~= "\n *";
263 					result ~= l[3 .. $];
264 				}
265 			}
266 		}
267 	} catch (Exception) {
268 	}
269 	return result;
270 }
271 
272 version (unittest):
273 struct Action {
274 	string name;
275 }
276 
277 @Action:
278 @ignore void foo(int a) {
279 }
280 
281 void func(int a, string b, double c) {
282 }
283 
284 uint[] search(string query, string order = "relevance", uint offset = 0, @"max results"uint limit = 10) => null;
285 /// Get the user info.
286 @"user info"@type("User") Object getUserInfo(@"uid"long id, @type("false") bool details = false) => null;
287 
288 uint count(@type("Uint32Array") uint[] ids) => 0;
289 /// multiline
290 /// comment
291 uint countNums(uint[] arr, size_t start, size_t end) => 0;