async Task <TypeResolver> PrepareAsync() { var trees = _trees.ToList(); //TODO: Temp workaround.. investigate why referencing annotations assembly is nor working properly var attributes = CSharpSyntaxTree.ParseText(@" using System; namespace WebTyped.Annotations { [AttributeUsage(AttributeTargets.Class)] public class ClientTypeAttribute : Attribute { public ClientTypeAttribute(string typeName = null, string module = null) {} } [AttributeUsage(AttributeTargets.Parameter)] public class NamedTupleAttribute : Attribute { public NamedTupleAttribute() {} } } namespace System.Web.Http { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false)] public class FromUriAttribute : Attribute { public string Name { get; set; } public Type BinderType { get; set; } public bool SuppressPrefixCheck { get; set; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false)] public class FromBodyAttribute : Attribute {} } namespace Microsoft.AspNetCore.Mvc{ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false)] public class FromQueryAttribute : Attribute { public string Name { get; set; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false)] public class FromRouteAttribute : Attribute { public string Name { get; set; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false)] public class FromBodyAttribute : Attribute {} } "); //References var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); //var webTypedAnnotations = MetadataReference.CreateFromFile(typeof(ClientTypeAttribute).Assembly.Location); var systemRuntime = MetadataReference.CreateFromFile(typeof(int).Assembly.Location); var linqExpressions = MetadataReference.CreateFromFile(typeof(IQueryable).Assembly.Location); //Nullable var thisAssembly = MetadataReference.CreateFromFile(this.GetType().Assembly.Location); //External assemblies var externals = new List <PortableExecutableReference>(); foreach (var path in assemblies) { if (File.Exists(path)) { externals.Add(MetadataReference.CreateFromFile(path)); } } //var nugetGlobalPackages = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) // ? "%userprofile%/.nuget/packages" // : "~/.nuget/packages"; if (packages.Any()) { //Finding nuget global-packages //var process = Process.Start("dotnet", "nuget locals global-packages --list"); //process.BeginOutputReadLine(); Process process = new Process { StartInfo = { FileName = "dotnet", Arguments = "nuget locals global-packages --list", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; string globalPackagePath = ""; process.EnableRaisingEvents = true; process.OutputDataReceived += (s, e) => { if (string.IsNullOrWhiteSpace(globalPackagePath)) { globalPackagePath = e.Data; } }; process.ErrorDataReceived += (s, e) => Debug.WriteLine($@"Error: {e.Data}"); process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); process.WaitForExit(); globalPackagePath = globalPackagePath.Replace("info : global-packages: ", ""); if (string.IsNullOrWhiteSpace(globalPackagePath)) { Console.WriteLine($"Nuget global-packages not found"); } else { foreach (var pkg in packages) { if (string.IsNullOrWhiteSpace(pkg.Version) && string.IsNullOrWhiteSpace(pkg.Csproj)) { throw new Exception($"{pkg.Name} version not informed"); } var version = pkg.Version; if (!string.IsNullOrWhiteSpace(pkg.Csproj)) { var reference = XDocument .Load(pkg.Csproj) .Descendants() .FirstOrDefault(d => d.Name.LocalName == "PackageReference" && d.Attribute("Include").Value == pkg.Name); if (reference != null) { version = reference.Attribute("Version").Value; } } //var nugetGlobalPackages = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/.nuget/packages"; var pkgDir = $"{globalPackagePath}{pkg.Name}/{version}".ToLower(); if (!Directory.Exists(pkgDir)) { Console.WriteLine($"Package {pkg.Name} not found in {pkgDir}"); continue; } var dllPaths = Directory.GetFiles(pkgDir, $"*.dll", SearchOption.AllDirectories); var grouped = dllPaths.GroupBy(path => Path.GetFileName(path)); foreach (var g in grouped) { externals.Add(MetadataReference.CreateFromFile(g.First())); } //if(dllPath != null) //{ // externals.Add(MetadataReference.CreateFromFile(dllPath)); //} } } } var compilation = CSharpCompilation.Create( "Comp", //Trees trees .Union(new SyntaxTree[] { attributes }) , //basic + external Assemblies new[] { mscorlib, systemRuntime, linqExpressions, //MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location), //thisAssembly /*, webTypedAnnotations*/ } .Union(externals) ); var typeResolver = new TypeResolver(_options); var tasks = new List <Task>(); var namedTypeSymbols = new ConcurrentBag <INamedTypeSymbol>(); var semanticModels = trees.ToDictionary(t => t, t => compilation.GetSemanticModel(t)); foreach (var t in trees) { tasks.Add(t.GetRootAsync().ContinueWith(tks => { var root = tks.Result; foreach (var @type in root.DescendantNodes().OfType <BaseTypeDeclarationSyntax>()) { var sm = semanticModels[t]; namedTypeSymbols.Add(sm.GetDeclaredSymbol(@type)); } })); } //Types in referenced assemblies var assembliesTypesMatcher = new Matcher(); referenceTypes.ToList().ForEach(t => assembliesTypesMatcher.AddInclude(t)); //var allSymbols = compilation.GetSymbolsWithName((str) => true); foreach (var e in externals) { //var ass = Assembly.ReflectionOnlyLoadFrom(Path.GetFullPath(e.FilePath)); var assSymbol = compilation.GetAssemblyOrModuleSymbol(e) as IAssemblySymbol; //var tm = assSymbol.GlobalNamespace.GetTypeMembers(); var named = GetNamedTypeSymbols(assSymbol.GlobalNamespace);// assSymbol.GlobalNamespace.GetMembers().OfType<INamedTypeSymbol>(); //var fullNames = named.Select(n => n.ToString().Split(" ").Last()); foreach (var n in named) { try { var fullName = n.ToString().Split(" ").Last(); if (assembliesTypesMatcher.Match(fullName).HasMatches) { namedTypeSymbols.Add(n); } } catch { } } //foreach(var fn in fullNames) //{ // try // { // if (assembliesTypesMatcher.Match(fn).HasMatches) // { // var name = fn; // var nts = assSymbol.GetTypeByMetadataName(name); // namedTypeSymbols.Add(nts); // } // } // catch { } //} //var named = assSymbol.GlobalNamespace.GetMembers().OfType<INamedTypeSymbol>(); //var typeNames = ass.GetTypes().Select(t => t.FullName); //foreach(var tn in typeNames) //{ // try // { // if (assembliesTypesMatcher.Match(tn).HasMatches) // { // namedTypeSymbols.Add(assSymbol.GetTypeByMetadataName(tn)); // } // } // catch { } //} } foreach (var tsk in tasks) { await tsk; } foreach (var s in namedTypeSymbols) { if (Service.IsService(s)) { typeResolver.Add(new Service(s, typeResolver, _options)); continue; } if (Model.IsModel(s)) { typeResolver.Add(new Model(s, typeResolver, _options)); continue; } if (TsEnum.IsEnum(s)) { typeResolver.Add(new TsEnum(s, typeResolver, _options)); continue; } } return(typeResolver); }
public ParameterResolution(IParameterSymbol parameterSymbol, TypeResolver typeResolver, ResolutionContext context, Options options) { var p = parameterSymbol; var type = p.Type; //it it is generic type, try get some constraint type var cTypes = (type as ITypeParameterSymbol)?.ConstraintTypes; if (cTypes.HasValue) { type = cTypes.Value.First(); } var attrs = p.GetAttributes(); //Removing tuple support...it' not worth it //var hasNamedTupleAttr = attrs.Any(a => a.AttributeClass.Name == nameof(NamedTupleAttribute)); var res = typeResolver.Resolve(type, context /*, hasNamedTupleAttr*/); this.Name = p.Name; this.FromName = p.Name; this.SearchRelayFormat = this.Name; var fromAttr = attrs.FirstOrDefault(a => a.AttributeClass.Name.StartsWith("From")); // if (fromAttr != null) { switch (fromAttr.AttributeClass.Name) { case "FromUriAttribute": case "FromQueryAttribute": case "FromBodyAttribute": case "FromRouteAttribute": this.From = (ParameterFromKind)typeof(ParameterFromKind).GetField(fromAttr.AttributeClass.Name.Replace("Attribute", "")).GetValue(null); switch (fromAttr.AttributeClass.Name) { case "FromUriAttribute": case "FromQueryAttribute": KeyValuePair <string, TypedConstant>?nameArg = fromAttr.NamedArguments.ToList().FirstOrDefault(na => na.Key == "Name"); if (nameArg.HasValue) { var tConst = nameArg.Value.Value; if (tConst.Value != null) { this.FromName = tConst.Value.ToString(); this.SearchRelayFormat = $"{this.FromName}: {this.Name}"; } } break; } break; } } //Check if it is a Model being used to catch query/route parameters if (type.IsReferenceType) { var props = new List <string>(); var outProps = new List <string>(); var hasModifications = false; List <ISymbol> members = new List <ISymbol>(); var t = type; while (t != null) { members.AddRange(t.GetMembers()); t = t.BaseType; } foreach (var m in members) { if (m.Kind != SymbolKind.Field && m.Kind != SymbolKind.Property) { continue; } if (m.DeclaredAccessibility != Accessibility.Public) { continue; } if (((m as IFieldSymbol)?.IsConst).GetValueOrDefault()) { continue; } var name = m.Name; if (!options.KeepPropsCase && !((m as IFieldSymbol)?.IsConst).GetValueOrDefault()) { name = name.ToCamelCase(); } if (m is IPropertySymbol) { //allProps.Add(m.Name); var propAttrs = m.GetAttributes(); var propFromAttr = propAttrs.FirstOrDefault(a => a.AttributeClass.Name.StartsWith("From")); if (propFromAttr != null) { switch (propFromAttr.AttributeClass.Name) { case "FromRouteAttribute": hasModifications = true; //ignoredQueryProps.Add(m.Name); break; case "FromUriAttribute": case "FromQueryAttribute": KeyValuePair <string, TypedConstant>?nameArg = propFromAttr.NamedArguments.ToList().FirstOrDefault(na => na.Key == "Name"); if (nameArg.HasValue) { var tConst = nameArg.Value.Value; if (tConst.Value != null) { //renamedQueryProps.Add(p.Name, tConst.Value.ToString()); outProps.Add($"{tConst.Value.ToString()}: {this.Name}.{name}"); hasModifications = true; } } break; default: props.Add($"{name}: {this.Name}.{name}"); break; } } else { props.Add($"{name}: {this.Name}.{name}"); } } } if (hasModifications) { this.SearchRelayFormat = ""; if (props.Any()) { //Pensar melhor, no asp.net podemos colocar como parametros varias models com props de nomes iguais. //Por esse motivo fazemos o obj: {} //Ao mesmo tempo, isso é uma modelagem esquisita de api. Talvez devemos dar preferencia mesmo para a segunda opção //onde fica tudo na "raiz". Além disso, não testei ainda o comportamento do asp.net quando multiplos parametros //que clasheiam sujas props //O maior motivo, é que no caso de uma model que possui alguns itens na raiz, ficando ora model.coisa e $coisa2 //por ex, o asp.net se perde em seu modelbinding, considerando apenas no model.<algo>. //this.SearchRelayFormat = $"...({this.Name} ? {{ {this.FromName}: {{ {string.Join(", ", props)} }} }} : {{}})"; this.SearchRelayFormat = $"...({this.Name} ? {{ {string.Join(", ", props)} }} : {{}})"; } if (outProps.Any()) { if (props.Any()) { //Add comma this.SearchRelayFormat += ", "; } this.SearchRelayFormat += $"...({this.Name} ? {{{string.Join(", ", outProps)}}} : {{}})"; } } } string typeName = res.Declaration; if (TsEnum.IsEnum(type)) { if (res.IsEnum) { var enumNames = string .Join( " | ", type.GetMembers() .Where(m => m.Kind == SymbolKind.Field) .Select(m => $"'{m.Name}'")); if (!string.IsNullOrEmpty(enumNames)) { typeName = $"{typeName} | {enumNames}"; } } } this.Type = res; this.IsOptional = p.IsOptional; this.Signature = $"{p.Name}{(p.IsOptional ? "?" : "")}: {typeName}" + (res.IsNullable ? " | null" : ""); this.Ignore = p.GetAttributes().Any(a => a.AttributeClass.Name == "FromServices"); }