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;