reinforced-typings

How to point typings (by groups) to different class and/or namespace?


I am able to generate typings for all sets of apis in a code base, however, I am not able to separate them based on certain characteristics into various classes and/or namespaces. As an example business requirement, I want three distinct classes (and/or namespaces) based on certain characteristics for the types, regardless of where they are coming from. Public (Services), Private (Services), and Internal (Services), but I want to generate the typings at runtime. Here is what I have so far. I have tried overriding the classes in FunClassCodeGenerator(). It works, but generates duplicate classes and as mentioned in some readings AFAIK, there are not partial classes in typescript. All the experimental code and comments have been removed.

// etc this is prepopulated.....
var newApiClasses = new List<Type>(); 
            builder.ExportAsClasses(newApiClasses, conf => conf
                .FlattenHierarchy()
                .Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
                .WithCodeGenerator<FunClassCodeGenerator>()
                .WithMethods(p => !p.GetCustomAttributes<AreaAttribute>().Any()  
                                  && !p.DeclaringType.GetCustomAttributes<AreaAttribute>().Any(),p => p.WithCodeGenerator<AngularActionCallGenerator>())
    
                .AddImport("{ Injectable }", "@angular/core")
                .AddImport("{ Observable }", "rxjs")
                .AddImport("{ Router }", "@angular/router")
                .Decorator(@"Injectable({
            providedIn: 'root'
})" + "\r\n")
                .DontIncludeToNamespace(true)
                .ExportTo(FileName($"{nameof(Routes)}.test")));
   public static class ServicesGenerator
    {
        public class FunClassCodeGenerator : ClassCodeGenerator
        {
            public override RtClass GenerateNode(Type element, RtClass result, TypeResolver resolver)
            {
                var r = base.GenerateNode(element, result, resolver);

                Console.WriteLine($"\n\n\n\n" +
                                  $"Information 102: {JsonConvert.SerializeObject(r.Name, Formatting.Indented)}" +
                                  $"\n\n\n\n");

                // constructor (router: Router) { }
                r.Members.Add(new RtConstructor()
                {
                    Order = 500,
                    Body = new RtRaw("this.router = router;"),
                    Arguments = new List<RtArgument>()
                    {
                        new RtArgument()
                        {
                            // not sure if this is private... private 
                            Identifier = new RtIdentifier("router"),
                            Type = new RtSimpleTypeName("Router")
                        }
                    },
                });

                // router: Router;
                r.Members.Add(new RtField()
                {
                    Order = -1,
                    Identifier = new RtIdentifier("router"),
                    Type = new RtSimpleTypeName("Router"),
                    LineAfter = "\r\n"
                });

                return r;
            }
        }

        public class AngularActionCallGenerator : MethodCodeGenerator
        {
            public override RtFunction GenerateNode(MethodInfo element, RtFunction result, TypeResolver resolver)
            {
                Console.Write($"104: {nameof(MethodCodeGenerator)} me first");

                if (IsNotWanted(element))
                    return null;

                Console.WriteLine($"Information 500: {JsonConvert.SerializeObject(result, Formatting.Indented)}");

                result = base.GenerateNode(element, result, resolver);
                if (result == null)
                    return null;
                result.Order = 1000;

                Console.WriteLine($"Information 501: {JsonConvert.SerializeObject(result, Formatting.Indented)}");

                result.IsStatic = false;

                var hasArea = element.GetCustomAttributes<AreaAttribute>().Any();
                var isHttpPost = element.GetCustomAttributes<HttpPostAttribute>().Any();
                var isHttpGet = !element.GetCustomAttributes<HttpPostAttribute>().Any();
                var isAuthOnly = !element.GetCustomAttributes<AllowAnonymousAttribute>().Any()
                                 && (element.GetCustomAttributes<AuthorizeAttribute>().Any()
                                     || element.DeclaringType?.GetCustomAttributes<AuthorizeAttribute>().Any() == true);

                // here we are overriding return type to corresponding promise
                var retType = result.ReturnType;
                var isVoid = retType is RtSimpleTypeName && ((RtSimpleTypeName)retType).TypeName == "void";

                // we use TypeResolver to get "any" type to avoid redundant type name construction
                // (or because I'm too lazy to manually construct "any" type)
                if (isVoid)
                    retType = resolver.ResolveTypeName(typeof(object));

                // Here we override TS method return type to make it angular.IPromise
                // We are using RtSimpleType with generic parameter of existing method type
                result.ReturnType = new RtSimpleTypeName(new[] { retType }, "", $"Promise");
                var flatReturnType = $"Promise<{retType}>";

                var p = element.GetParameters().Select(c => string.Format("'{0}': {0}", c.Name));

                var dataParameters = string.Join(", ", p);

                // Here we get path to controller
                // It is quite simple solution requiring /{controller}/{action} route
                var area = element.DeclaringType?.GetCustomAttributes<AreaAttribute>().FirstOrDefault()
                    ?.RouteValue;
                var controller = element.DeclaringType?.Name.Replace("Controller", string.Empty);
                var url = string.Join('/', new[] { "api", area, controller, element.Name }.Where(p2 => !string.IsNullOrWhiteSpace(p2)));

                var postOptions = $@", {{
    method: 'POST', {(dataParameters.Length > 0 ? $"\r\n\tbody: JSON.stringify({$"{{{dataParameters}}}"})," : string.Empty)}
    headers: {{
        'Content-Type': 'application/json'
    }}
}}";
                var getOptions = dataParameters.Length > 0
                    ? $@" + new URLSearchParams({{ {string.Join(", ", element.GetParameters().Select(c => string.Format("{0}: {0}.toString()", c.Name)))}}})"
                    : string.Empty;

                var fetch = $@"return fetch(`{url}{(getOptions.Length > 0 ? "?" : string.Empty)}`{(isHttpPost ? postOptions : getOptions)})
    .then(res => {{ var result = res.json(); console.log('this.router.url', this.router.url); console.log('result', res, result); if (res.redirected) {{ this.router.navigateByUrl(new URL(res.url).pathname) }} return result as {flatReturnType} }});";

                var code =
                    $"// IsHttpPost: {isHttpPost}" +
                    $"\r\n" +
                    $"// IsHttpGet: {isHttpGet}" +
                    $"\r\n" +
                    $"// IsAuthOnly: {isAuthOnly}" +
                    $"\r\n" +
                    $"{fetch}"
                    + (false
                        ? "\r\n" +
                          "// was: var params = {{ {dataParameters} }}; return this.http.post('{url}', params).then((response) => {{ response.data['requestParams'] = params; return response.data; }});"
                        : string.Empty);

                var body = new RtRaw(code);
                result.Body = body;
                result.LineAfter = "\r\n";

                return result;
            }
        }

        public static bool IsNotWanted(MethodInfo element)
        {
            if (element == null)
                return true;

            if (element.IsDefined(typeof(NonActionAttribute)))
                return true;

            if (element.IsFamily)
                return true;

            // element.GetBaseDefinition().DeclaringType == element.DeclaringType && , typeof(ControllerBase), typeof(Controller)
            if (new[] { typeof(IDisposable), typeof(IActionFilter), typeof(IAsyncActionFilter) }.Contains(element.ReflectedType))
                return true;

            if (element.IsPrivate)
                return true;

            if (element.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true).Any()) // || element.IsOverride())
                return true;

            return false;
        }
    }

Solution

  • The following is a rough (but correct) answer to my own question. Each of these statements takes a number of types (classes) and their methods and comfortably separates them based on criterion. Please note the generator will still generate (empty) classes regardless of them having any eligible members (methods) or not. This is why we are using a where clause in the parameter for types as well as using the WithMethods (with a parameter) in the second set of code.

    builder.ExportAsClasses(types: allTypes.Where(p => p.IsSubclassOf(typeof(BaseAreaApiController)) 
                                                            && p.IsSubclassOf(typeof(BaseMembersAreaApiController))).ToList(), configuration: conf => conf
                    .Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
                    .WithCodeGenerator<FunClassCodeGenerator>()
                    .WithPublicMethods(p => p.WithCodeGenerator<AngularActionCallGenerator>())
                    .AddImport("{ Injectable }", "@angular/core")
                    .AddImport("{ Observable }", "rxjs")
                    .AddImport("{ Router }", "@angular/router")
                    .Decorator("Injectable({ providedIn: 'root' })" + "\r\n")
                    .OverrideNamespace("AreaServices")
                    .ExportTo(FileName($"services.private")));
    
    builder.ExportAsClasses(allTypes.Where(p => !p.IsSubclassOf(typeof(BaseAreaApiController))).ToList(), conf => conf
                    .Substitute(typeof(DateTime), new RtSimpleTypeName("Date"))
                    .WithCodeGenerator<FunClassCodeGenerator>()
                    .WithMethods(p => !p.GetCustomAttributes<AuthorizeAttribute>().Any(), p2 => p2.WithCodeGenerator<AngularActionCallGenerator>())
                    .AddImport("{ Injectable }", "@angular/core")
                    .AddImport("{ Observable }", "rxjs")
                    .AddImport("{ Router }", "@angular/router")
                    .Decorator("Injectable({ providedIn: 'root' })" + "\r\n")
                    .OverrideNamespace("PublicServices")
                    .ExportTo(FileName($"services.public")));