BadScript 2
Loading...
Searching...
No Matches
BadInteropObjectModelBuilder.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Collections.Immutable;
4using System.Linq;
6using Microsoft.CodeAnalysis;
7
9
11{
12 private static readonly DiagnosticDescriptor s_CanNotConvertTypeDiagnostic = new DiagnosticDescriptor("BAS0001",
13 "Can not convert type",
14 "Can not convert type {0}",
15 "BadScript2.Interop.Generator",
16 DiagnosticSeverity.Error,
17 true
18 );
19
20 private static readonly DiagnosticDescriptor s_CanNotStringifyDefaultValue = new DiagnosticDescriptor("BAS0002",
21 "Can not stringify default value",
22 "Can not stringify default value {0}",
23 "BadScript2.Interop.Generator",
24 DiagnosticSeverity.Error,
25 true
26 );
27
28 private readonly List<Diagnostic> m_Diagnostics = new List<Diagnostic>();
29
30 private void AddDiagnostic(Diagnostic diagnostic)
31 {
32 m_Diagnostics.Add(diagnostic);
33 }
34
35 private IEnumerable<IMethodSymbol> FindMethods(INamedTypeSymbol api)
36 {
37 IEnumerable<IMethodSymbol> methods = api.GetMembers()
38 .Where(x => x is IMethodSymbol)
39 .Cast<IMethodSymbol>();
40
41 foreach (IMethodSymbol method in methods)
42 {
43 AttributeData? attribute = method.GetInteropMethodAttribute();
44
45 if (attribute != null)
46 {
47 yield return method;
48 }
49 }
50 }
51
52 private IEnumerable<IPropertySymbol> FindProperties(INamedTypeSymbol api)
53 {
54 IEnumerable<IPropertySymbol> methods = api.GetMembers()
55 .Where(x => x is IPropertySymbol)
56 .Cast<IPropertySymbol>();
57
58 foreach (IPropertySymbol method in methods)
59 {
60 AttributeData? attribute = method.GetInteropPropertyAttribute();
61
62 if (attribute != null)
63 {
64 yield return method;
65 }
66 }
67 }
68
69 public ObjectModel GenerateModel(INamedTypeSymbol api)
70 {
71 IEnumerable<IMethodSymbol> methods = FindMethods(api);
72 IEnumerable<IPropertySymbol> properties = FindProperties(api);
73 AttributeData? apiAttribute = api.GetInteropObjectAttribute();
74
75 if (apiAttribute == null)
76 {
77 throw new Exception("BadInteropObjectAttribute not found");
78 }
79
80 string apiName = api.Name;
81 string? baseClassName = null;
82 if (apiAttribute.ConstructorArguments.Length > 0)
83 {
84 apiName = apiAttribute.ConstructorArguments[0]
85 .Value?.ToString() ??
86 apiName;
87 }
88 if(apiAttribute.ConstructorArguments.Length > 1)
89 {
90 var t = apiAttribute.ConstructorArguments[1].Value;
91 baseClassName = $"{t}";
92 }
93
94 MethodModel[] methodModels = GenerateMethodModels(methods)
95 .ToArray();
96 PropertyModel[] propertyModels = GeneratePropertyModels(properties).ToArray();
97 Diagnostic[] diagnostics = [];
98
99 var ctorModel = GenerateConstructorModel(api.Constructors.FirstOrDefault(x => x.DeclaredAccessibility == Accessibility.Public && !x.IsStatic));
100
101
102 if (m_Diagnostics.Count != 0)
103 {
104 diagnostics = m_Diagnostics.ToArray();
105 m_Diagnostics.Clear();
106 }
107
108 return new ObjectModel(api.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat
109 .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle
110 .Omitted
111 )
112 )!,
113 api.Name,
114 methodModels,
115 apiName,
116 diagnostics,
117 propertyModels,
118 ctorModel,
119 baseClassName
120 );
121 }
122
123 private string EscapeDescription(string str)
124 {
125 return str.Replace("\\", "\\\\")
126 .Replace("\"", "\\\"")
127 .Replace("\n", "\\n");
128 }
129
130 private IEnumerable<ParameterModel> GenerateParameterModel(IMethodSymbol method)
131 {
132 foreach (IParameterSymbol symbol in method.Parameters.Where(x => x.Ordinal >= 0)
133 .OrderBy(x => x.Ordinal))
134 {
135 //if parameter is first parameter and its of type BadScript2.Runtime.BadExecutionContext
136 if (symbol.Ordinal == 0 && symbol.Type.ToDisplayString() == "BadScript2.Runtime.BadExecutionContext")
137 {
138 yield return new ParameterModel(true);
139 }
140 else
141 {
142 AttributeData? attribute = symbol.GetParameterAttribute();
143 string? name = symbol.Name;
144 string? description = null;
145 bool isNullable = symbol.NullableAnnotation == NullableAnnotation.Annotated;
146 string type = ConvertType(symbol.Type, true, symbol);
147
148 if (attribute != null)
149 {
150 ImmutableArray<TypedConstant> cargs = attribute.ConstructorArguments;
151
152 name = cargs.Length > 0
153 ? cargs[0]
154 .Value?.ToString() ??
155 name
156 : name;
157
158 description = cargs.Length > 1
159 ? cargs[1]
160 .Value?.ToString()
161 : null;
162
163 if (description != null)
164 {
165 description = EscapeDescription(description);
166 }
167
168 type = cargs.Length > 2
169 ? cargs[2]
170 .Value?.ToString() ??
171 type
172 : type;
173 }
174
175 bool hasDefaultValue = symbol.HasExplicitDefaultValue;
176 string? defaultValue = null;
177
178 if (hasDefaultValue)
179 {
180 defaultValue = StringifyDefaultValue(symbol.ExplicitDefaultValue, symbol);
181 }
182
183 bool isRestArgs = symbol.IsParams;
184
185 yield return new ParameterModel(false,
186 hasDefaultValue,
187 defaultValue,
188 name,
189 description,
190 type,
191 symbol.Type.ToDisplayString(),
192 isNullable,
193 isRestArgs
194 );
195 }
196 }
197 }
198
199 private string StringifyDefaultValue(object? obj, ISymbol symbol)
200 {
201 switch (obj)
202 {
203 case string str:
204 return $"\"{str}\"";
205 case char c:
206 return $"'{c}'";
207 case bool b:
208 return b.ToString()
209 .ToLower();
210 case float f:
211 return $"{f}f";
212 case double d:
213 return $"{d}d";
214 case decimal m:
215 return $"{m}m";
216 case int i:
217 return i.ToString();
218 case long l:
219 return $"{l}L";
220 case uint u:
221 return $"{u}u";
222 case ulong ul:
223 return $"{ul}ul";
224 case short s:
225 return $"{s}s";
226 case ushort us:
227 return $"{us}us";
228 case byte by:
229 return $"{by}b";
230 case sbyte sb:
231 return $"{sb}sb";
232 case Enum e:
233 return $"{e.GetType().Name}.{e}";
234 case Type t:
235 return $"typeof({t.Name})";
236 case null:
237 return "null";
238 default:
239 {
240 AddDiagnostic(symbol.CreateDiagnostic(s_CanNotStringifyDefaultValue, obj));
241
242 return "null";
243 }
244 }
245 }
246
247 private IEnumerable<PropertyModel> GeneratePropertyModels(IEnumerable<IPropertySymbol> symbols)
248 {
249 foreach (IPropertySymbol symbol in symbols)
250 {
251 AttributeData? attribute = symbol.GetInteropPropertyAttribute();
252
253 if (attribute == null)
254 {
255 continue;
256 }
257
258 ImmutableArray<TypedConstant> cargs = attribute.ConstructorArguments;
259
260 //The api name, if its not provided, use the method name
261 string name = cargs.Length > 0
262 ? cargs[0]
263 .Value?.ToString() ??
264 symbol.Name
265 : symbol.Name;
266
267 //The description, if its not provided, use null
268 string description = EscapeDescription(cargs.Length > 1
269 ? cargs[1]
270 .Value?.ToString() ??
271 string.Empty
272 : string.Empty
273 );
274 bool readOnly = cargs.Length > 2 && (cargs[2].Value as bool? ?? false);
275
276 bool allowNativeReturn = cargs.Length > 3 && (cargs[3].Value as bool? ?? false);
277
278 //The return type, if its not provided, use the symbol's return type
279 string returnType = ConvertType(symbol.Type, allowNativeReturn, symbol);
280
281 var model = new PropertyModel(symbol.Name, name, returnType, description, readOnly || symbol.SetMethod == null, symbol.Type.ToDisplayString());
282
283 yield return model;
284 }
285 }
286
287 private MethodModel GenerateConstructorModel(IMethodSymbol? symbol)
288 {
289 if (symbol == null)
290 {
291 return new MethodModel(".ctor", ".ctor", "", "", [], false, "", false);
292 }
293
294 return new MethodModel(".ctor", ".ctor", "", "", GenerateParameterModel(symbol).ToArray(), false, "", false);
295 }
296
297 private IEnumerable<MethodModel> GenerateMethodModels(IEnumerable<IMethodSymbol> symbols)
298 {
299 foreach (IMethodSymbol symbol in symbols)
300 {
301 AttributeData? attribute = symbol.GetInteropMethodAttribute();
302
303 if (attribute == null)
304 {
305 continue;
306 }
307
308 ImmutableArray<TypedConstant> cargs = attribute.ConstructorArguments;
309
310 //The api name, if its not provided, use the method name
311 string name = cargs.Length > 0
312 ? cargs[0]
313 .Value?.ToString() ??
314 symbol.Name
315 : symbol.Name;
316
317 //The description, if its not provided, use null
318 string description = EscapeDescription(cargs.Length > 1
319 ? cargs[1]
320 .Value?.ToString() ??
321 string.Empty
322 : string.Empty
323 );
324
325 //Check if the symbol is a void return
326 bool isVoidReturn = symbol.ReturnsVoid;
327 AttributeData? returnAttribute = symbol.GetReturnTypeAttribute();
328 string returnDescription = string.Empty;
329 bool allowNativeReturn = false;
330
331 if (returnAttribute != null)
332 {
333 if (returnAttribute.ConstructorArguments.Length > 0)
334 {
335 returnDescription = EscapeDescription(returnAttribute.ConstructorArguments[0]
336 .Value?.ToString() ??
337 string.Empty
338 );
339 }
340
341 if (returnAttribute.ConstructorArguments.Length > 1)
342 {
343 allowNativeReturn = (bool)returnAttribute.ConstructorArguments[1].Value!;
344 }
345 }
346
347 //The return type, if its not provided, use the symbol's return type
348 string returnType = isVoidReturn ? "any" : ConvertType(symbol.ReturnType, allowNativeReturn, symbol);
349
350 MethodModel model = new MethodModel(symbol.Name,
351 name,
352 returnType,
353 description,
355 .ToArray(),
356 isVoidReturn,
357 returnDescription,
358 allowNativeReturn
359 );
360
361 yield return model;
362 }
363 }
364
365 private string ConvertType(ITypeSymbol type, bool allowAny, ISymbol sourceSymbol)
366 {
367 if (type.NullableAnnotation == NullableAnnotation.Annotated)
368 {
369 //Unwrap nullable value types
370 type = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
371 }
372
373 //If type is string, return "string"
374 if (type.SpecialType == SpecialType.System_String)
375 {
376 return "string";
377 }
378
379 //if type is bool return "bool"
380 if (type.SpecialType == SpecialType.System_Boolean)
381 {
382 return "bool";
383 }
384
385 //if type is numeric type return "num"
386 if (type.SpecialType == SpecialType.System_Byte ||
387 type.SpecialType == SpecialType.System_SByte ||
388 type.SpecialType == SpecialType.System_Int16 ||
389 type.SpecialType == SpecialType.System_UInt16 ||
390 type.SpecialType == SpecialType.System_Int32 ||
391 type.SpecialType == SpecialType.System_UInt32 ||
392 type.SpecialType == SpecialType.System_Int64 ||
393 type.SpecialType == SpecialType.System_UInt64 ||
394 type.SpecialType == SpecialType.System_Single ||
395 type.SpecialType == SpecialType.System_Double ||
396 type.SpecialType == SpecialType.System_Decimal)
397 {
398 return "num";
399 }
400
401 //if type is array or list or ilist, return "array"
402 if (type is IArrayTypeSymbol ||
403 type.AllInterfaces.Any(x => x.ToDisplayString() == "System.Collections.Generic.IList<T>") ||
404 type.AllInterfaces.Any(x => x.ToDisplayString() == "System.Collections.IList<T>"))
405 {
406 return "Array";
407 }
408
409 //if type is BadObject, return "any"
410 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.BadObject")
411 {
412 return "any";
413 }
414
415 if (type.ToDisplayString() == "BadScript2.Interop.Common.Task.BadTask")
416 {
417 return "Task";
418 }
419
420 //If type is BadTable, return "Table"
421 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.BadTable")
422 {
423 return "Table";
424 }
425
426 //If type is BadArray return "Array"
427 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.BadArray")
428 {
429 return "Array";
430 }
431
432 //If type is BadFunction return "Function"
433 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Functions.BadFunction")
434 {
435 return "Function";
436 }
437
438 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Types.BadClassPrototype")
439 {
440 return "Type";
441 }
442
443 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Error.BadRuntimeError")
444 {
445 return "Error";
446 }
447
448 //If Type is IBadBoolean or BadBoolean return "bool"
449 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.IBadBoolean" ||
450 type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.BadBoolean")
451 {
452 return "bool";
453 }
454
455 //If Type is IBadNumber or BadNumber return "num"
456 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.IBadNumber" ||
457 type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.BadNumber")
458 {
459 return "num";
460 }
461
462 //If Type is IBadString or BadString return "string"
463 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.IBadString" ||
464 type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.BadString")
465 {
466 return "string";
467 }
468
469 //If Type is BadScope return "Scope"
470 if (type.ToDisplayString() == "BadScript2.Runtime.BadScope")
471 {
472 return "Scope";
473 }
474
475 if (allowAny)
476 {
477 return "any";
478 }
479
480 AddDiagnostic(sourceSymbol.CreateDiagnostic(s_CanNotConvertTypeDiagnostic, type.ToDisplayString()));
481
482 return "any";
483 }
484}
IEnumerable< IPropertySymbol > FindProperties(INamedTypeSymbol api)
string ConvertType(ITypeSymbol type, bool allowAny, ISymbol sourceSymbol)
IEnumerable< PropertyModel > GeneratePropertyModels(IEnumerable< IPropertySymbol > symbols)
IEnumerable< ParameterModel > GenerateParameterModel(IMethodSymbol method)
IEnumerable< MethodModel > GenerateMethodModels(IEnumerable< IMethodSymbol > symbols)