Last active
January 28, 2025 17:47
-
-
Save Fasteroid/71ece35783c21b34a7ba7f11cc62add4 to your computer and use it in GitHub Desktop.
The ultimate Typewriter template for C# files that have models in them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
${ | |
using Typewriter.VisualStudio; | |
static ILog console; | |
Template(Settings settings){ | |
console = settings.Log; | |
} | |
/* | |
__TypewriterModels.tst v1.2.0 - https://gist.github.com/Fasteroid | |
Features: | |
- Automatically imports type dependencies from the same assembly | |
- Handles generics and primitive arrays | |
- Handles nullable types | |
- Ignores properties with [JsonIgnore] | |
- Leaves casing alone | |
- Nullables are translated as `?` instead of `| null` | |
- Supports overrides for system types in `IncludeOverrides` | |
- Now with console logging! | |
*/ | |
static Dictionary<string, string> IncludeOverrides = new Dictionary<string, string> { | |
{ "FileContentResult", "Special/FileContentResult" }, | |
{ "FileResult", "Special/FileContentResult" } | |
}; | |
static string NamespaceToInclude = "my_api.Models"; | |
static string AssemblyName = "my-api"; | |
public static T ForceAccess<T>(object obj, string propertyName) | |
{ | |
return (T) obj.GetType() | |
.GetProperty(propertyName) | |
.GetValue(obj); | |
} | |
string ClassWithGenerics(Class c){ | |
return ( (Type) c ).Name; | |
} | |
string OriginalName(Type t){ | |
return t.OriginalName.Replace("?",""); | |
} | |
IEnumerable<Type> GetMetaClasses(Type t){ | |
var meta = new List<Type>(){t}; | |
if( t.IsGeneric || t.IsEnumerable ){ | |
meta.AddRange( t.TypeArguments.SelectMany(GetMetaClasses) ); | |
} | |
if( t.BaseClass != null ){ | |
meta.AddRange( GetMetaClasses(t.BaseClass) ); | |
} | |
return meta.Where( IsIncludable ).ToList(); | |
} | |
bool IsIncludable(Type t){ | |
return ( t.AssemblyName == AssemblyName || IncludeOverrides.ContainsKey(OriginalName(t)) ) & ( t.ContainingClass == null ); // no containing class = not a generic placeholder | |
} | |
IEnumerable<Type> GetIncludableTypes( IEnumerable<Type> types, File f ){ | |
return types.SelectMany(t => GetMetaClasses(t)) | |
.Where(t => IsIncludable(t)) | |
.Where(t => | |
!f.Classes.Any( c => c.Name == OriginalName(t) ) && | |
!f.Enums.Any( e => e.Name == OriginalName(t) ) && | |
!AutoInterfaces(f.Interfaces).Any( i => i.Name == OriginalName(t) ) | |
); | |
} | |
string LastNPaths(string path, int n){ | |
return string.Join("\\", path.Split('\\').Reverse().Take(n).Reverse()); | |
} | |
string FileSpecificImports(File f){ | |
List<Type> typeIncludes = new List<Type>(); | |
List<string> fileIncludes = new List<string>(); | |
foreach( Class c in f.Classes ){ | |
typeIncludes.AddRange( GetIncludableTypes( | |
c.Properties | |
.Where( p => !p.Attributes.Any(a => a.Name.StartsWith("JsonIgnore")) ) | |
.Select( p => p.Type ), | |
f | |
)); | |
typeIncludes.AddRange( GetIncludableTypes( | |
AutoInterfaces(c.Interfaces) | |
.Select( i => i.Type ), | |
f | |
)); | |
typeIncludes.AddRange( GetIncludableTypes( GetMetaClasses(c), f ) ); | |
fileIncludes.AddRange( c.Properties | |
.Where( p => p.Attributes.Any(a => a.Name.StartsWith("JsonInject")) ) | |
.Select( p => (String) p.Attributes.Where(a => a.Name.StartsWith("JsonInject")).First().Arguments.First().Value ) | |
.Select( s => $"import {{ {s} }} from '../Special/{s}';") | |
); | |
} | |
foreach( Interface i in f.Interfaces ){ | |
typeIncludes.AddRange( GetIncludableTypes( | |
i.Properties | |
.Where( p => !p.Attributes.Any(a => a.Name.StartsWith("JsonIgnore")) ) | |
.Select(p => p.Type), | |
f | |
)); | |
typeIncludes.AddRange( GetIncludableTypes( GetMetaClasses(i), f ) ); | |
typeIncludes.AddRange( GetIncludableTypes( | |
AutoInterfaces(i.Interfaces) | |
.Select( i => i.Type ), | |
f | |
)); | |
fileIncludes.AddRange( i.Properties | |
.Where( p => p.Attributes.Any(a => a.Name.StartsWith("JsonInject")) ) | |
.Select( p => (String) p.Attributes.Where(a => a.Name.StartsWith("JsonInject")).First().Arguments.First().Value ) | |
.Select( s => $"import {{ {s} }} from '../Special/{s}';") | |
); | |
} | |
fileIncludes.AddRange( typeIncludes.Select( (t) => { | |
if( IncludeOverrides.ContainsKey(OriginalName(t)) ){ | |
return $"import {{ {OriginalName(t)} }} from '../{IncludeOverrides[OriginalName(t)]}';"; | |
} | |
var path = LastNPaths( t.FileLocations.First(), 2 ); | |
path = path.Replace("\\","/"); // fix for windows paths | |
path = path.Substring(0, path.LastIndexOf('.')); // remove file extension | |
return $"import {{ {OriginalName(t)} }} from '../{path.Replace("\\","/")}';"; | |
})); | |
return String.Join( Environment.NewLine, fileIncludes.Distinct() ); | |
} | |
bool IsNullable(Property p){ | |
return p.Type.IsNullable; | |
} | |
string TypeNotNull(Property p){ | |
var inject = p.Attributes.Where( a => a.Name.StartsWith("JsonInject") ); | |
if( inject.Count() > 0 ){ | |
return (String) inject.First().Arguments.First().Value; // lie, but actually tell the truth | |
} | |
string t = p.Type.ToString(); | |
return t.Replace(" | null",""); | |
} | |
string Optional(Property p){ | |
return IsNullable(p) ? "?" : ""; | |
} | |
string BaseType(Class c){ | |
return c.BaseClass == null ? "" : $" {ClassWithGenerics(c.BaseClass)} & "; | |
} | |
string BaseInterfaces(Interface i){ | |
return String.Join( "", i.Interfaces.Select( ii => $"{ii.Name} & " ) ); | |
} | |
IEnumerable<Interface> AutoInterfaces(IInterfaceCollection those){ | |
return those.Where( ii => ii.Attributes.Any( a => a.Name == "AutoImplementable" ) ); | |
} | |
string AutoInterfacesList(Class c){ | |
return String.Join( "", AutoInterfaces(c.Interfaces).Select( i => $"{i.Name} & " ) ); | |
} | |
bool ShouldRenderInterface(Interface i){ | |
return i.Namespace == NamespaceToInclude && i.Attributes.Any( a => a.Name == "AutoImplementable" ); | |
} | |
} | |
///////////////////////////////////////////////////////////////////////////////////////////////////// | |
// WARNING: THIS IS AN AUTO-GENERATED FILE - DO NOT MODIFY IT! YOUR CHANGES WILL BE OVERWRITTEN! // | |
// MODIFY THE TEMPLATE (.tst) FILE INSTEAD! // | |
// https://github.com/AdaskoTheBeAsT/Typewriter // | |
///////////////////////////////////////////////////////////////////////////////////////////////////// | |
$FileSpecificImports | |
$Classes(c => c.Namespace == NamespaceToInclude)[ | |
export type $ClassWithGenerics = $BaseType$AutoInterfacesList{ $Properties(p => !p.Attributes.Any(a => a.Name == "JsonIgnore"))[ | |
$Name$Optional: $TypeNotNull;] | |
} | |
] | |
$Interfaces(i => ShouldRenderInterface(i))[ | |
// Do not use directly. | |
export type $Name = $BaseInterfaces{ $Properties(p => !p.Attributes.Any(a => a.Name == "JsonIgnore"))[ | |
$Name$Optional: $TypeNotNull;] | |
} | |
] | |
$Enums(e => e.Namespace == NamespaceToInclude)[ | |
export enum $Name { $Values[ | |
$Name = $Value,] | |
} | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment