diff --git a/T4TS.Build/Models.cs b/T4TS.Build/Models.cs index 548c69f..aa5515d 100644 --- a/T4TS.Build/Models.cs +++ b/T4TS.Build/Models.cs @@ -12,16 +12,21 @@ public class Barfoo public DateTime DateTime { get; set; } } - [TypeScriptInterface(Module = "Fooz")] + [TypeScriptInterface(Module = "Fooz", NamePrefix = "I")] public class Foobar { [TypeScriptMember(Name = "OverrideAll", Optional = true, Type = "bool")] public string SomeString { get; set; } public Foobar Recursive { get; set; } + public int? NullableInt { get; set; } + public double? NullableDouble { get; set; } public Barfoo[] NestedObjectArr { get; set; } public List NestedObjectList { get; set; } public string[][] TwoDimensions { get; set; } public Barfoo[][][] ThreeDimensions { get; set; } + + [TypeScriptMember(CamelCase = true)] + public int CamelCasePlease { get; set; } } [TypeScriptInterface(Name = "OverridenName")] diff --git a/T4TS.Build/T4TS.d.ts b/T4TS.Build/T4TS.d.ts index 97b68b2..5a2adc2 100644 --- a/T4TS.Build/T4TS.d.ts +++ b/T4TS.Build/T4TS.d.ts @@ -3,30 +3,33 @@ ****************************************************************************/ // -- Begin global interfaces -/** Generated from T4TS.Build.Barfoo **/ -interface Barfoo { - Number: number; - Complex: T4TS.OverridenName; - Name: string; - DateTime: string; -} + /** Generated from T4TS.Build.Barfoo **/ + interface Barfoo { + Number: number; + Complex: T4TS.OverridenName; + Name: string; + DateTime: string; + } // -- End global interfaces module Fooz { /** Generated from T4TS.Build.Foobar **/ - export interface Foobar { + export interface IFoobar { OverrideAll?: bool; - Recursive: Fooz.Foobar; + Recursive: Fooz.IFoobar; + NullableInt?: number; + NullableDouble?: number; NestedObjectArr: Barfoo[]; NestedObjectList: Barfoo[]; TwoDimensions: string[][]; ThreeDimensions: Barfoo[][][]; + camelCasePlease: number; } } module T4TS { /** Generated from T4TS.Build.Inherited **/ - export interface OverridenName { + export interface OverridenName { OtherName?: string; Integers: number[]; Doubles: number[]; @@ -34,23 +37,23 @@ module T4TS { [index: number]: Barfoo; } /** Generated from T4TS.Build.InheritanceTest1 **/ - export interface InheritanceTest1 extends /* global interface */Barfoo { + export interface InheritanceTest1 extends Barfoo { SomeString: string; - Recursive: Fooz.Foobar; + Recursive: Fooz.IFoobar; } /** Generated from T4TS.Build.InheritanceTest2 **/ export interface InheritanceTest2 extends T4TS.InheritanceTest1 { SomeString2: string; - Recursive2: Fooz.Foobar; + Recursive2: Fooz.IFoobar; } /** Generated from T4TS.Build.InheritanceTest3 **/ export interface InheritanceTest3 extends T4TS.OverridenName { SomeString3: string; - Recursive3: Fooz.Foobar; + Recursive3: Fooz.IFoobar; } /** Generated from T4TS.Build.InheritanceTest4 **/ - export interface InheritanceTest4 { + export interface InheritanceTest4 { SomeString4: string; - Recursive4: Fooz.Foobar; + Recursive4: Fooz.IFoobar; } } diff --git a/T4TS.Build/T4TS.tt b/T4TS.Build/T4TS.tt index 4908361..0f3543d 100644 --- a/T4TS.Build/T4TS.tt +++ b/T4TS.Build/T4TS.tt @@ -1,37 +1,19 @@ -<#@ template language="C#" debug="false" hostspecific="true" #> +<#@ template language="C#" debug="true" hostspecific="true" #> <#@ output extension=".d.ts" #> <#@ assembly name="System.Core" #> -<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0"#> -<#@ assembly name="EnvDTE"#> -<#@ assembly name="EnvDTE80"#> +<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #> +<#@ assembly name="EnvDTE" #> +<#@ assembly name="EnvDTE80" #> <#@ import namespace="System.Collections.Generic" #> -<#@ import namespace="System.IO" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> +<#@ import namespace="EnvDTE" #> <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> -<#@ import namespace="EnvDTE" #> -/**************************************************************************** - Generated by T4TS.tt - don't make any changes in this file -****************************************************************************/ -<# foreach(var module in GetDataToRender()) { #> - -<#= module.IsGlobal ? "// -- Begin global interfaces" : "module "+module.QualifiedName + " {" #> -<# foreach(var tsInterface in module.Interfaces) { #> -<#= module.IsGlobal? "" : " " #>/** Generated from <#= tsInterface.FullName #> **/ -<#= module.IsGlobal? "" : " " #><#= module.IsGlobal?"":"export "#>interface <#= tsInterface.Name #> <#= tsInterface.Parent != null ? "extends " + (tsInterface.Parent.Module.IsGlobal ? "/* global interface */" : tsInterface.Parent.Module.QualifiedName + ".") + tsInterface.Parent.Name : "" #> { -<# foreach(var member in tsInterface.Members) { #> - <#= module.IsGlobal? "" : " " #><#= member.Name + (member.Optional ? "?" : "") #>: <#=member.Type #>; -<# } #> -<# if (tsInterface.IndexedType != null) { #> - <#= module.IsGlobal? "" : " " #>[index: number]: <#= tsInterface.IndexedType #>; -<# } #> -<#= module.IsGlobal? "" : " " #>} -<# } #> -<#= module.IsGlobal ? "// -- End global interfaces" : "}" #> -<# } #> -<#@ Include File="T4TS.tt.settings.t4"#> -<#+ +<#@ Include File="T4TS.tt.settings.t4" #> +<#= + OutputFormatter.GetOutput(GetDataToRender()) #><#+ + List GetDataToRender() { DTE dte = null; @@ -53,10 +35,12 @@ List GetDataToRender() { var settings = new Settings { DefaultModule = DefaultModule, - DefaultOptional = DefaultOptional + DefaultOptional = DefaultOptional, + DefaultCamelCaseMemberNames = DefaultCamelCaseMemberNames, + DefaultInterfaceNamePrefix = DefaultInterfaceNamePrefix }; - var generator = new CodeGenerator(project, settings); + var generator = new CodeTraverser(project, settings); return generator.GetAllInterfaces().ToList(); } @@ -78,563 +62,784 @@ Project GetProjectContainingT4File(DTE dte) { return projectItem.ContainingProject; } -// -- Models ---------------------------------------------------------------------------------- + public class CodeTraverser + { + public Project Project { get; private set; } + public Settings Settings { get; private set; } -class Settings -{ - /// - /// The default module of the generated interface, if not specified by the TypeScriptInterfaceAttribute - /// - public string DefaultModule { get; set; } + private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; + private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; - /// - /// The default value for Optional, if not specified by the TypeScriptMemberAttribute - /// - public bool DefaultOptional { get; set; } -} -class TypeScriptInterface -{ - public string Name { get; set; } - public string FullName { get; set; } + public CodeTraverser(Project project, Settings settings) + { + if (project == null) + throw new ArgumentNullException("project"); - public List Members { get; set; } - public TypescriptType IndexedType { get; set; } - public TypeScriptInterface Parent { get; set; } - public TypeScriptModule Module { get; set; } + if (settings == null) + throw new ArgumentNullException("settings"); - public TypeScriptInterface() - { - Members = new List(); - } -} -class TypeScriptInterfaceAttributeValues -{ - public string Module { get; set; } - public string Name { get; set; } -} -class TypeScriptInterfaceMember -{ - public string Name { get; set; } - public TypescriptType Type { get; set; } - public bool Optional { get; set; } - public string FullName { get; set; } -} -class TypeScriptMemberAttributeValues -{ - public string Name { get; set; } - public bool Optional { get; set; } - public string Type { get; set; } -} -class TypeScriptModule -{ - public string QualifiedName { get; set; } - public List Interfaces { get; set; } - public bool IsGlobal - { - get { return string.IsNullOrWhiteSpace(QualifiedName); } - } + this.Project = project; + this.Settings = settings; + } - public TypeScriptModule() - { - Interfaces = new List(); - } -} + public TypeContext BuildContext() + { + var typeContext = new TypeContext(); -// -- Traversal ---------------------------------------------------------------------------------- + new ProjectTraverser(this.Project, (ns) => + { + new NamespaceTraverser(ns, (codeClass) => + { + CodeAttribute attribute; + if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) + return; -class ClassTraverser -{ - public CodeClass CodeClass { get; private set; } - public Action WithProperty { get; set; } + var values = GetInterfaceValues(codeClass, attribute); + var customType = new CustomType(GetInterfaceName(values), values.Module); - public ClassTraverser(CodeClass codeClass, Action withProperty) - { - if (codeClass == null) - throw new ArgumentNullException("codeClass"); - - if (withProperty == null) - throw new ArgumentNullException("withProperty"); + typeContext.AddCustomType(codeClass.FullName, customType); + }); + }); - this.CodeClass = codeClass; - this.WithProperty = withProperty; + return typeContext; + } - if (codeClass.Members != null) - Traverse(codeClass.Members); - } + public IEnumerable GetAllInterfaces() + { + var typeContext = BuildContext(); + var byModuleName = new Dictionary(); + var tsMap = new Dictionary(); - private void Traverse(CodeElements members) - { - foreach (var property in members.OfType()) - WithProperty(property); - } -} -class NamespaceTraverser -{ - public Action WithCodeClass { get; private set; } + new ProjectTraverser(this.Project, (ns) => + { + new NamespaceTraverser(ns, (codeClass) => + { + if (codeClass.Attributes == null || codeClass.Attributes.Count == 0) + return; + + CodeAttribute attribute; + if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) + return; + + var values = GetInterfaceValues(codeClass, attribute); + + TypeScriptModule module; + if (!byModuleName.TryGetValue(values.Module, out module)) + { + module = new TypeScriptModule { QualifiedName = values.Module }; + byModuleName.Add(values.Module, module); + } + + var tsInterface = BuildInterface(codeClass, values, typeContext); + tsMap.Add(codeClass, tsInterface); + tsInterface.Module = module; + module.Interfaces.Add(tsInterface); + }); + }); - public NamespaceTraverser(CodeNamespace ns, Action withCodeClass) - { - if (ns == null) - throw new ArgumentNullException("ns"); - - if (withCodeClass == null) - throw new ArgumentNullException("withCodeClass"); - - WithCodeClass = withCodeClass; - - if (ns.Members != null) - Traverse(ns.Members); - } + var tsInterfaces = tsMap.Values.ToList(); + tsMap.Keys.ToList().ForEach(codeClass => + { + var parent = tsInterfaces.LastOrDefault(intf => codeClass.IsDerivedFrom[intf.FullName] && intf.FullName != codeClass.FullName); + if (parent != null) + tsMap[codeClass].Parent = parent; + }); - private void Traverse(CodeElements members) - { - foreach (var codeClass in members.OfType()) - WithCodeClass(codeClass); - } -} -class ProjectTraverser -{ - public Action WithNamespace { get; private set; } + return byModuleName.Values + .OrderBy(m => m.QualifiedName) + .ToList(); + } + + private string GetInterfaceName(TypeScriptInterfaceAttributeValues attributeValues) + { + if (!string.IsNullOrEmpty(attributeValues.NamePrefix)) + return attributeValues.NamePrefix + attributeValues.Name; - public ProjectTraverser(Project project, Action withNamespace) - { - if (project == null) - throw new ArgumentNullException("project"); - - if (withNamespace == null) - throw new ArgumentNullException("withNamespace"); + return attributeValues.Name; + } - WithNamespace = withNamespace; + private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) + { + var tsInterface = new TypeScriptInterface + { + FullName = codeClass.FullName, + Name = GetInterfaceName(attributeValues) + }; - if (project.ProjectItems != null) - Traverse(project.ProjectItems); - } + TypescriptType indexedType; + if (TryGetIndexedType(codeClass, typeContext, out indexedType)) + tsInterface.IndexedType = indexedType; - private void Traverse(ProjectItems items) - { - foreach (ProjectItem pi in items) + new ClassTraverser(codeClass, (property) => + { + TypeScriptInterfaceMember member; + if (TryGetMember(property, typeContext, out member)) + tsInterface.Members.Add(member); + }); + return tsInterface; + } + + private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) { - if (pi.FileCodeModel != null) + foreach (CodeAttribute attr in attributes) { - var codeElements = pi.FileCodeModel.CodeElements; - foreach (var ns in codeElements.OfType()) - WithNamespace(ns); + if (attr.FullName == attributeFullName) + { + attribute = attr; + return true; + } } - if (pi.ProjectItems != null) - Traverse(pi.ProjectItems); + attribute = null; + return false; } - } -} -// -- Types ---------------------------------------------------------------------------------- - -class TypescriptType -{ - public virtual string Name { get { return "any"; } } + private bool TryGetIndexedType(CodeClass codeClass, TypeContext typeContext, out TypescriptType indexedType) + { + indexedType = null; + if (codeClass.Bases == null || codeClass.Bases.Count == 0) + return false; - public override string ToString() - { - return Name; - } -} -class StringType: TypescriptType -{ - public override string Name - { - get { return "string"; } - } -} -class NumberType : TypescriptType -{ - public override string Name - { - get { return "number"; } - } -} -class BoolType: TypescriptType -{ - public override string Name - { - get { return "bool"; } - } -} -class ArrayType: TypescriptType -{ - public TypescriptType ElementType { get; set; } + foreach (CodeElement baseClass in codeClass.Bases) + { + if (typeContext.IsGenericEnumerable(baseClass.FullName)) + { + string fullName = typeContext.UnwrapGenericType(baseClass.FullName); + indexedType = typeContext.GetTypeScriptType(fullName); + return true; + } + } - public override string ToString() - { - return ElementType.ToString() + "[]"; - } -} -class CustomType: TypescriptType -{ - private string m_name; + return false; + } - public override string Name - { - get { return m_name; } - } - - public string QualifedModule { get; private set; } + private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + { + var values = GetAttributeValues(interfaceAttribute); - public CustomType(string name, string qualifiedModule=null) - { - m_name = name; - this.QualifedModule = qualifiedModule; - } + return new TypeScriptInterfaceAttributeValues + { + Name = values.ContainsKey("Name") ? values["Name"] : codeClass.Name, + Module = values.ContainsKey("Module") ? values["Module"] : Settings.DefaultModule ?? "T4TS", + NamePrefix = values.ContainsKey("NamePrefix") ? values["NamePrefix"] : Settings.DefaultInterfaceNamePrefix ?? string.Empty + }; + } - public override string ToString() - { - if (string.IsNullOrWhiteSpace(QualifedModule)) - return base.ToString(); + private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + { + member = null; + if (property.Access != vsCMAccess.vsCMAccessPublic) + return false; - return QualifedModule + "." + base.ToString(); - } -} -class TypeContext -{ - private static readonly string[] genericCollectionTypeStarts = new string[] { - "System.Collections.Generic.List<", - "System.Collections.Generic.IList<", - "System.Collections.Generic.ICollection<" - }; + var getter = property.Getter; + if (getter == null) + return false; - /// - /// Lookup table for "custom types", ie. non-builtin types. Keyed on the FullName of the type. - /// - private Dictionary customTypes = new Dictionary(); + var values = GetMemberValues(property, typeContext); + member = new TypeScriptInterfaceMember + { + Name = values.Name ?? property.Name, + FullName = property.FullName, + Optional = values.Optional, + Type = (string.IsNullOrWhiteSpace(values.Type)) + ? typeContext.GetTypeScriptType(getter.Type) + : new CustomType(values.Type) + }; - public void AddCustomType(string typeFullName, CustomType customType) - { - customTypes.Add(typeFullName, customType); - } + if (values.CamelCase && values.Name == null) + member.Name = member.Name.Substring(0, 1).ToLowerInvariant() + member.Name.Substring(1); - public bool TryGetCustomType(string typeFullName, out CustomType customType) - { - return customTypes.TryGetValue(typeFullName, out customType); - } + return true; + } - public TypescriptType GetTypeScriptType(CodeTypeRef codeType) - { - switch (codeType.TypeKind) + private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) { - case vsCMTypeRef.vsCMTypeRefChar: - case vsCMTypeRef.vsCMTypeRefString: - return new StringType(); + bool? attributeOptional = null; + bool? attributeCamelCase = null; + string attributeName = null; + string attributeType = null; - case vsCMTypeRef.vsCMTypeRefBool: - return new BoolType(); + CodeAttribute attribute; + if (TryGetAttribute(property.Attributes, MemberAttributeFullName, out attribute)) + { + var values = GetAttributeValues(attribute); + if (values.ContainsKey("Optional")) + attributeOptional = values["Optional"] == "true"; - case vsCMTypeRef.vsCMTypeRefByte: - case vsCMTypeRef.vsCMTypeRefDouble: - case vsCMTypeRef.vsCMTypeRefInt: - case vsCMTypeRef.vsCMTypeRefShort: - case vsCMTypeRef.vsCMTypeRefFloat: - case vsCMTypeRef.vsCMTypeRefLong: - case vsCMTypeRef.vsCMTypeRefDecimal: - return new NumberType(); + if (values.ContainsKey("CamelCase")) + attributeCamelCase = values["CamelCase"] == "true"; - default: - return TryResolveType(codeType); - } - } + values.TryGetValue("Name", out attributeName); + values.TryGetValue("Type", out attributeType); + } - private TypescriptType TryResolveType(CodeTypeRef codeType) - { - if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) - { - return new ArrayType() + return new TypeScriptMemberAttributeValues { - ElementType = GetTypeScriptType(codeType.ElementType) + Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, + Name = attributeName, + Type = attributeType, + CamelCase = attributeCamelCase ?? Settings.DefaultCamelCaseMemberNames }; } - return GetTypeScriptType(codeType.AsFullName); - } + private Dictionary GetAttributeValues(CodeAttribute codeAttribute) + { + var values = new Dictionary(); + foreach (CodeElement child in codeAttribute.Children) + { + var property = (EnvDTE80.CodeAttributeArgument)child; + if (property == null || property.Value == null) + continue; + + // remove quotes if the property is a string + string val = property.Value ?? string.Empty; + if (val.StartsWith("\"") && val.EndsWith("\"")) + val = val.Substring(1, val.Length - 2); - private ArrayType TryResolveEnumerableType(string typeFullName) + values.Add(property.Name, val); + } + + return values; + } + } + public class TypeContext { - return new ArrayType - { - ElementType = GetTypeScriptType(typeFullName) + private static readonly string[] genericCollectionTypeStarts = new string[] { + "System.Collections.Generic.List<", + "System.Collections.Generic.IList<", + "System.Collections.Generic.ICollection<" }; - } - public TypescriptType GetTypeScriptType(string typeFullName) - { - CustomType customType; - if (customTypes.TryGetValue(typeFullName, out customType)) - return customType; + private static readonly string nullableTypeStart = "System.Nullable<"; + + /// + /// Lookup table for "custom types", ie. non-builtin types. Keyed on the FullName of the type. + /// + private Dictionary customTypes = new Dictionary(); + + public void AddCustomType(string typeFullName, CustomType customType) + { + customTypes.Add(typeFullName, customType); + } + + public bool TryGetCustomType(string typeFullName, out CustomType customType) + { + return customTypes.TryGetValue(typeFullName, out customType); + } + + public TypescriptType GetTypeScriptType(CodeTypeRef codeType) + { + switch (codeType.TypeKind) + { + case vsCMTypeRef.vsCMTypeRefChar: + case vsCMTypeRef.vsCMTypeRefString: + return new StringType(); + + case vsCMTypeRef.vsCMTypeRefBool: + return new BoolType(); + + case vsCMTypeRef.vsCMTypeRefByte: + case vsCMTypeRef.vsCMTypeRefDouble: + case vsCMTypeRef.vsCMTypeRefInt: + case vsCMTypeRef.vsCMTypeRefShort: + case vsCMTypeRef.vsCMTypeRefFloat: + case vsCMTypeRef.vsCMTypeRefLong: + case vsCMTypeRef.vsCMTypeRefDecimal: + return new NumberType(); + + default: + return TryResolveType(codeType); + } + } + + private TypescriptType TryResolveType(CodeTypeRef codeType) + { + if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) + { + return new ArrayType() + { + ElementType = GetTypeScriptType(codeType.ElementType) + }; + } - if (IsGenericEnumerable(typeFullName)) + return GetTypeScriptType(codeType.AsFullName); + } + + private ArrayType TryResolveEnumerableType(string typeFullName) { return new ArrayType { - ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + ElementType = GetTypeScriptType(typeFullName) }; } - switch (typeFullName) + public TypescriptType GetTypeScriptType(string typeFullName) { - case "System.Double": - case "System.Int16": - case "System.Int32": - case "System.Int64": - case "System.UInt16": - case "System.UInt32": - case "System.UInt64": - case "System.Decimal": - case "System.Byte": - case "System.SByte": - case "System.Single": - return new NumberType(); + CustomType customType; + if (customTypes.TryGetValue(typeFullName, out customType)) + return customType; - case "System.String": - case "System.DateTime": - return new StringType(); + if (IsGenericEnumerable(typeFullName)) + { + return new ArrayType + { + ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } + else if (IsNullable(typeFullName)) + { + return new NullableType + { + WrappedType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } - default: - return new TypescriptType(); + switch (typeFullName) + { + case "System.Double": + case "System.Int16": + case "System.Int32": + case "System.Int64": + case "System.UInt16": + case "System.UInt32": + case "System.UInt64": + case "System.Decimal": + case "System.Byte": + case "System.SByte": + case "System.Single": + return new NumberType(); + + case "System.String": + case "System.DateTime": + return new StringType(); + + default: + return new TypescriptType(); + } } - } - public string UnwrapGenericType(string typeFullName) - { - int firstIndex = typeFullName.IndexOf('<'); - return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); - } + private bool IsNullable(string typeFullName) + { + return typeFullName.StartsWith(nullableTypeStart); + } - public bool IsGenericEnumerable(string typeFullName) - { - return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); + public string UnwrapGenericType(string typeFullName) + { + int firstIndex = typeFullName.IndexOf('<'); + return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); + } + + public bool IsGenericEnumerable(string typeFullName) + { + return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); + } } -} + public class InterfaceOutputAppender : OutputAppender + { + private bool InGlobalModule { get; set; } -// -- Code generation -------------------------------------------------------------------------- + public InterfaceOutputAppender(StringBuilder output, int baseIndentation, bool inGlobalModule) + : base(output, baseIndentation) + { + this.InGlobalModule = inGlobalModule; + } -class CodeGenerator -{ - public Project Project { get; private set; } - public Settings Settings { get; private set; } + public override void AppendOutput(TypeScriptInterface tsInterface) + { + BeginInterface(tsInterface); - private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; - private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; + AppendMembers(tsInterface); + + if (tsInterface.IndexedType != null) + AppendIndexer(tsInterface); - public CodeGenerator(Project project, Settings settings) - { - if (project == null) - throw new ArgumentNullException("project"); + EndInterface(); + } - if (settings == null) - throw new ArgumentNullException("settings"); + private void AppendMembers(TypeScriptInterface tsInterface) + { + var appender = new MemberOutputAppender(Output, BaseIndentation + 4); + foreach (var member in tsInterface.Members) + appender.AppendOutput(member); + } - this.Project = project; - this.Settings = settings; - } + private void BeginInterface(TypeScriptInterface tsInterface) + { + AppendIndentedLine("/** Generated from " + tsInterface.FullName + " **/"); - public TypeContext BuildContext() - { - var typeContext = new TypeContext(); + if (InGlobalModule) + AppendIndented("interface " + tsInterface.Name); + else + AppendIndented("export interface " + tsInterface.Name); + + if (tsInterface.Parent != null) + Output.Append(" extends " + (tsInterface.Parent.Module.IsGlobal ? "" : tsInterface.Parent.Module.QualifiedName + ".") + tsInterface.Parent.Name); + + Output.AppendLine(" {"); + } - new ProjectTraverser(this.Project, (ns) => + private void EndInterface() { - new NamespaceTraverser(ns, (codeClass) => - { - CodeAttribute attribute; - if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) - return; + AppendIndentedLine("}"); + } - var values = GetInterfaceValues(codeClass, attribute); - var customType = new CustomType(values.Name, values.Module); + private void AppendIndexer(TypeScriptInterface tsInterface) + { + AppendIndendation(); + Output.AppendFormat(" [index: number]: {0};", tsInterface.IndexedType); + Output.AppendLine(); + } + } + public class MemberOutputAppender : OutputAppender + { + public MemberOutputAppender(StringBuilder output, int baseIndentation) + : base(output, baseIndentation) + { + } - typeContext.AddCustomType(codeClass.FullName, customType); - }); - }); + public override void AppendOutput(TypeScriptInterfaceMember member) + { + AppendIndendation(); - return typeContext; - } + bool isOptional = member.Optional || (member.Type is NullableType); - public IEnumerable GetAllInterfaces() + Output.AppendFormat("{0}{1}: {2}", + member.Name, + (isOptional ? "?" : ""), + member.Type + ); + + Output.AppendLine(";"); + } + } + public class ModuleOutputAppender : OutputAppender { - var typeContext = BuildContext(); - var byModuleName = new Dictionary(); - var tsMap = new Dictionary(); + public ModuleOutputAppender(StringBuilder output, int baseIndentation) + : base(output, baseIndentation) + { + } - new ProjectTraverser(this.Project, (ns) => + public override void AppendOutput(TypeScriptModule module) { - new NamespaceTraverser(ns, (codeClass) => - { - if (codeClass.Attributes == null || codeClass.Attributes.Count == 0) - return; + BeginModule(module); - CodeAttribute attribute; - if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) - return; + var interfaceAppender = new InterfaceOutputAppender(Output, BaseIndentation + 4, module.IsGlobal); + foreach (var tsInterface in module.Interfaces) + interfaceAppender.AppendOutput(tsInterface); - var values = GetInterfaceValues(codeClass, attribute); + EndModule(module); + } - TypeScriptModule module; - if (!byModuleName.TryGetValue(values.Module, out module)) - { - module = new TypeScriptModule { QualifiedName = values.Module }; - byModuleName.Add(values.Module, module); - } + private void BeginModule(TypeScriptModule module) + { + if (module.IsGlobal) + { + Output.AppendLine("// -- Begin global interfaces"); + } + else + { + Output.Append("module "); + Output.Append(module.QualifiedName); + Output.AppendLine(" {"); + } + } - var tsInterface = BuildInterface(codeClass, values, typeContext); - tsMap.Add(codeClass, tsInterface); - tsInterface.Module = module; - module.Interfaces.Add(tsInterface); - }); - }); - - var tsInterfaces = tsMap.Values.ToList(); - tsMap.Keys.ToList().ForEach(codeClass => - { - var parent = tsInterfaces.LastOrDefault(intf => codeClass.IsDerivedFrom[intf.FullName] && intf.FullName != codeClass.FullName); - if (parent != null) - tsMap[codeClass].Parent = parent; - }); - - return byModuleName.Values - .OrderBy(m => m.QualifiedName) - .ToList(); + private void EndModule(TypeScriptModule module) + { + if (module.IsGlobal) + Output.AppendLine("// -- End global interfaces"); + else + Output.AppendLine("}"); + } } - - private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) + public abstract class OutputAppender where TSegment: class { - var tsInterface = new TypeScriptInterface + protected StringBuilder Output { get; private set; } + protected int BaseIndentation { get; private set; } + + public OutputAppender(StringBuilder output, int baseIndentation) { - FullName = codeClass.FullName, - Name = attributeValues.Name - }; + if (output == null) + throw new ArgumentNullException("output"); + + this.Output = output; + this.BaseIndentation = baseIndentation; + } - TypescriptType indexedType; - if (TryGetIndexedType(codeClass, typeContext, out indexedType)) - tsInterface.IndexedType = indexedType; + public abstract void AppendOutput(TSegment segment); - new ClassTraverser(codeClass, (property) => + protected void AppendIndented(string text) { - TypeScriptInterfaceMember member; - if (TryGetMember(property, typeContext, out member)) - tsInterface.Members.Add(member); - }); - return tsInterface; - } + AppendIndendation(); + Output.Append(text); + } + + protected void AppendIndentedLine(string line) + { + AppendIndendation(); + Output.AppendLine(line); + } - private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) + protected void AppendIndendation() + { + Output.Append(' ', BaseIndentation); + } + + public override string ToString() + { + return Output.ToString(); + } + } + public static class OutputFormatter { - foreach (CodeAttribute attr in attributes) + public static string GetOutput(List modules) { - if (attr.FullName == attributeFullName) + var output = new StringBuilder(); + + output.AppendLine("/****************************************************************************"); + output.AppendLine(" Generated by T4TS.tt - don't make any changes in this file"); + output.AppendLine("****************************************************************************/"); + + var moduleAppender = new ModuleOutputAppender(output, 0); + foreach (var module in modules) { - attribute = attr; - return true; + output.AppendLine(); + moduleAppender.AppendOutput(module); } + + return output.ToString(); } + } + public class Settings + { + /// + /// The default module of the generated interface, if not specified by the TypeScriptInterfaceAttribute + /// + public string DefaultModule { get; set; } + + /// + /// The default value for Optional, if not specified by the TypeScriptMemberAttribute + /// + public bool DefaultOptional { get; set; } + + /// + /// The default value for the CamelCase flag for an interface member name, if not specified by the TypeScriptMemberAttribute + /// + public bool DefaultCamelCaseMemberNames { get; set; } - attribute = null; - return false; + /// + /// The default string to prefix interface names with. For instance, you might want to prefix the names with an "I" to get conventional interface names. + /// + public string DefaultInterfaceNamePrefix { get; set; } } + public class TypeScriptInterface + { + public string Name { get; set; } + public string FullName { get; set; } + + public List Members { get; set; } + public TypescriptType IndexedType { get; set; } + public TypeScriptInterface Parent { get; set; } + public TypeScriptModule Module { get; set; } - private bool TryGetIndexedType(CodeClass codeClass, TypeContext typeContext, out TypescriptType indexedType) + public TypeScriptInterface() + { + Members = new List(); + } + } + public class TypeScriptInterfaceAttributeValues { - indexedType = null; - if (codeClass.Bases == null || codeClass.Bases.Count == 0) - return false; + public string Module { get; set; } + public string Name { get; set; } + public string NamePrefix { get; set; } + } + public class TypeScriptInterfaceMember + { + public string Name { get; set; } + public TypescriptType Type { get; set; } + public bool Optional { get; set; } + public string FullName { get; set; } + } + public class TypeScriptMemberAttributeValues + { + public string Name { get; set; } + public bool Optional { get; set; } + public string Type { get; set; } + public bool CamelCase { get; set; } + } + public class TypeScriptModule + { + public string QualifiedName { get; set; } + public List Interfaces { get; set; } - foreach (CodeElement baseClass in codeClass.Bases) + /// + /// Returns true if this is the global namespace (ie. no module name) + /// + public bool IsGlobal { - if (typeContext.IsGenericEnumerable(baseClass.FullName)) - { - string fullName = typeContext.UnwrapGenericType(baseClass.FullName); - indexedType = typeContext.GetTypeScriptType(fullName); - return true; - } + get { return string.IsNullOrWhiteSpace(QualifiedName); } } - return false; + public TypeScriptModule() + { + Interfaces = new List(); + } } - - private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + public class ClassTraverser { - var values = GetAttributeValues(interfaceAttribute); + public CodeClass CodeClass { get; private set; } + public Action WithProperty { get; set; } - return new TypeScriptInterfaceAttributeValues + public ClassTraverser(CodeClass codeClass, Action withProperty) { - Name = values.ContainsKey("Name") ? values["Name"] : codeClass.Name, - Module = values.ContainsKey("Module") ? values["Module"] : Settings.DefaultModule ?? "T4TS", - }; + if (codeClass == null) + throw new ArgumentNullException("codeClass"); + + if (withProperty == null) + throw new ArgumentNullException("withProperty"); + + this.CodeClass = codeClass; + this.WithProperty = withProperty; + + if (codeClass.Members != null) + Traverse(codeClass.Members); + } + + private void Traverse(CodeElements members) + { + foreach (var property in members.OfType()) + WithProperty(property); + } } + public class NamespaceTraverser + { + public Action WithCodeClass { get; private set; } - private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + public NamespaceTraverser(CodeNamespace ns, Action withCodeClass) + { + if (ns == null) + throw new ArgumentNullException("ns"); + + if (withCodeClass == null) + throw new ArgumentNullException("withCodeClass"); + + WithCodeClass = withCodeClass; + + if (ns.Members != null) + Traverse(ns.Members); + } + + private void Traverse(CodeElements members) + { + foreach (var codeClass in members.OfType()) + WithCodeClass(codeClass); + } + } + public class ProjectTraverser { - member = null; - if (property.Access != vsCMAccess.vsCMAccessPublic) - return false; + public Action WithNamespace { get; private set; } - var getter = property.Getter; - if (getter == null) - return false; + public ProjectTraverser(Project project, Action withNamespace) + { + if (project == null) + throw new ArgumentNullException("project"); + + if (withNamespace == null) + throw new ArgumentNullException("withNamespace"); - var values = GetMemberValues(property, typeContext); - member = new TypeScriptInterfaceMember + WithNamespace = withNamespace; + + if (project.ProjectItems != null) + Traverse(project.ProjectItems); + } + + private void Traverse(ProjectItems items) { - Name = values.Name ?? property.Name, - FullName = property.FullName, - Optional = values.Optional, - Type = (string.IsNullOrWhiteSpace(values.Type)) - ? typeContext.GetTypeScriptType(getter.Type) - : new CustomType(values.Type) - }; + foreach (ProjectItem pi in items) + { + if (pi.FileCodeModel != null) + { + var codeElements = pi.FileCodeModel.CodeElements; + foreach (var ns in codeElements.OfType()) + WithNamespace(ns); + } - return true; + if (pi.ProjectItems != null) + Traverse(pi.ProjectItems); + } + } } + public class ArrayType: TypescriptType + { + public TypescriptType ElementType { get; set; } - private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) + public override string ToString() + { + return ElementType.ToString() + "[]"; + } + } + public class BoolType: TypescriptType { - bool? attributeOptional = null; - string attributeName = null; - string attributeType = null; + public override string Name + { + get { return "bool"; } + } + } + public class CustomType: TypescriptType + { + private string m_name; - CodeAttribute attribute; - if (TryGetAttribute(property.Attributes, MemberAttributeFullName, out attribute)) + public override string Name { - var values = GetAttributeValues(attribute); - if (values.ContainsKey("Optional")) - attributeOptional = values["Optional"] == "true"; + get { return m_name; } + } + + public string QualifedModule { get; private set; } - values.TryGetValue("Name", out attributeName); - values.TryGetValue("Type", out attributeType); + public CustomType(string name, string qualifiedModule=null) + { + m_name = name; + this.QualifedModule = qualifiedModule; } - return new TypeScriptMemberAttributeValues + public override string ToString() { - Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, - Name = attributeName, - Type = attributeType - }; + if (string.IsNullOrWhiteSpace(QualifedModule)) + return base.ToString(); + + return QualifedModule + "." + base.ToString(); + } } + public class NullableType : TypescriptType + { + public TypescriptType WrappedType { get; set; } - private Dictionary GetAttributeValues(CodeAttribute codeAttribute) + public override string ToString() + { + return WrappedType.ToString(); + } + } + public class NumberType : TypescriptType { - var values = new Dictionary(); - foreach (CodeElement child in codeAttribute.Children) + public override string Name { - var property = (EnvDTE80.CodeAttributeArgument)child; - if (property == null || property.Value == null) - continue; - - // remove quotes if the property is a string - string val = property.Value ?? string.Empty; - if (val.StartsWith("\"") && val.EndsWith("\"")) - val = val.Substring(1, val.Length - 2); - - values.Add(property.Name, val); + get { return "number"; } } + } + public class StringType: TypescriptType + { + public override string Name + { + get { return "string"; } + } + } + public class TypescriptType + { + public virtual string Name { get { return "any"; } } - return values; + public override string ToString() + { + return Name; + } } -} #> \ No newline at end of file diff --git a/T4TS.Build/T4TS.tt.settings.t4 b/T4TS.Build/T4TS.tt.settings.t4 index 43965d5..d3f697d 100644 --- a/T4TS.Build/T4TS.tt.settings.t4 +++ b/T4TS.Build/T4TS.tt.settings.t4 @@ -15,4 +15,13 @@ const string DefaultModule = "T4TS"; // generated member will look like "member?: type" instead of "member: type". const bool DefaultOptional = false; +// The default value for the CamelCase flag for an interface member name. +// If set to true, the first character of member names will be lower cased. +const bool DefaultCamelCaseMemberNames = false; + +// The default string to prefix interface names with. For instance, you +// might want to prefix the names with an "I" to get conventional +// interface names. +const string DefaultInterfaceNamePrefix = ""; + #> \ No newline at end of file diff --git a/T4TS.Example/T4TS.d.ts b/T4TS.Example/T4TS.d.ts index 31827bc..ced8892 100644 --- a/T4TS.Example/T4TS.d.ts +++ b/T4TS.Example/T4TS.d.ts @@ -29,17 +29,17 @@ module Fooz { module T4TS { /** Generated from T4TS.Example.Models.InheritanceTest1 **/ - export interface InheritanceTest1 { + export interface InheritanceTest1 extends Barfoo { SomeString: string; Recursive: Fooz.IFoobar; } /** Generated from T4TS.Example.Models.InheritanceTest2 **/ - export interface InheritanceTest2 { + export interface InheritanceTest2 extends T4TS.InheritanceTest1 { SomeString2: string; Recursive2: Fooz.IFoobar; } /** Generated from T4TS.Example.Models.InheritanceTest3 **/ - export interface InheritanceTest3 { + export interface InheritanceTest3 extends T4TS.OverridenName { SomeString3: string; Recursive3: Fooz.IFoobar; } diff --git a/build/T4TS.Attributes.dll b/build/T4TS.Attributes.dll index ff7ce89..0e67b6d 100644 Binary files a/build/T4TS.Attributes.dll and b/build/T4TS.Attributes.dll differ diff --git a/build/T4TS.tt b/build/T4TS.tt index 4908361..0f3543d 100644 --- a/build/T4TS.tt +++ b/build/T4TS.tt @@ -1,37 +1,19 @@ -<#@ template language="C#" debug="false" hostspecific="true" #> +<#@ template language="C#" debug="true" hostspecific="true" #> <#@ output extension=".d.ts" #> <#@ assembly name="System.Core" #> -<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0"#> -<#@ assembly name="EnvDTE"#> -<#@ assembly name="EnvDTE80"#> +<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #> +<#@ assembly name="EnvDTE" #> +<#@ assembly name="EnvDTE80" #> <#@ import namespace="System.Collections.Generic" #> -<#@ import namespace="System.IO" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> +<#@ import namespace="EnvDTE" #> <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> -<#@ import namespace="EnvDTE" #> -/**************************************************************************** - Generated by T4TS.tt - don't make any changes in this file -****************************************************************************/ -<# foreach(var module in GetDataToRender()) { #> - -<#= module.IsGlobal ? "// -- Begin global interfaces" : "module "+module.QualifiedName + " {" #> -<# foreach(var tsInterface in module.Interfaces) { #> -<#= module.IsGlobal? "" : " " #>/** Generated from <#= tsInterface.FullName #> **/ -<#= module.IsGlobal? "" : " " #><#= module.IsGlobal?"":"export "#>interface <#= tsInterface.Name #> <#= tsInterface.Parent != null ? "extends " + (tsInterface.Parent.Module.IsGlobal ? "/* global interface */" : tsInterface.Parent.Module.QualifiedName + ".") + tsInterface.Parent.Name : "" #> { -<# foreach(var member in tsInterface.Members) { #> - <#= module.IsGlobal? "" : " " #><#= member.Name + (member.Optional ? "?" : "") #>: <#=member.Type #>; -<# } #> -<# if (tsInterface.IndexedType != null) { #> - <#= module.IsGlobal? "" : " " #>[index: number]: <#= tsInterface.IndexedType #>; -<# } #> -<#= module.IsGlobal? "" : " " #>} -<# } #> -<#= module.IsGlobal ? "// -- End global interfaces" : "}" #> -<# } #> -<#@ Include File="T4TS.tt.settings.t4"#> -<#+ +<#@ Include File="T4TS.tt.settings.t4" #> +<#= + OutputFormatter.GetOutput(GetDataToRender()) #><#+ + List GetDataToRender() { DTE dte = null; @@ -53,10 +35,12 @@ List GetDataToRender() { var settings = new Settings { DefaultModule = DefaultModule, - DefaultOptional = DefaultOptional + DefaultOptional = DefaultOptional, + DefaultCamelCaseMemberNames = DefaultCamelCaseMemberNames, + DefaultInterfaceNamePrefix = DefaultInterfaceNamePrefix }; - var generator = new CodeGenerator(project, settings); + var generator = new CodeTraverser(project, settings); return generator.GetAllInterfaces().ToList(); } @@ -78,563 +62,784 @@ Project GetProjectContainingT4File(DTE dte) { return projectItem.ContainingProject; } -// -- Models ---------------------------------------------------------------------------------- + public class CodeTraverser + { + public Project Project { get; private set; } + public Settings Settings { get; private set; } -class Settings -{ - /// - /// The default module of the generated interface, if not specified by the TypeScriptInterfaceAttribute - /// - public string DefaultModule { get; set; } + private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; + private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; - /// - /// The default value for Optional, if not specified by the TypeScriptMemberAttribute - /// - public bool DefaultOptional { get; set; } -} -class TypeScriptInterface -{ - public string Name { get; set; } - public string FullName { get; set; } + public CodeTraverser(Project project, Settings settings) + { + if (project == null) + throw new ArgumentNullException("project"); - public List Members { get; set; } - public TypescriptType IndexedType { get; set; } - public TypeScriptInterface Parent { get; set; } - public TypeScriptModule Module { get; set; } + if (settings == null) + throw new ArgumentNullException("settings"); - public TypeScriptInterface() - { - Members = new List(); - } -} -class TypeScriptInterfaceAttributeValues -{ - public string Module { get; set; } - public string Name { get; set; } -} -class TypeScriptInterfaceMember -{ - public string Name { get; set; } - public TypescriptType Type { get; set; } - public bool Optional { get; set; } - public string FullName { get; set; } -} -class TypeScriptMemberAttributeValues -{ - public string Name { get; set; } - public bool Optional { get; set; } - public string Type { get; set; } -} -class TypeScriptModule -{ - public string QualifiedName { get; set; } - public List Interfaces { get; set; } - public bool IsGlobal - { - get { return string.IsNullOrWhiteSpace(QualifiedName); } - } + this.Project = project; + this.Settings = settings; + } - public TypeScriptModule() - { - Interfaces = new List(); - } -} + public TypeContext BuildContext() + { + var typeContext = new TypeContext(); -// -- Traversal ---------------------------------------------------------------------------------- + new ProjectTraverser(this.Project, (ns) => + { + new NamespaceTraverser(ns, (codeClass) => + { + CodeAttribute attribute; + if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) + return; -class ClassTraverser -{ - public CodeClass CodeClass { get; private set; } - public Action WithProperty { get; set; } + var values = GetInterfaceValues(codeClass, attribute); + var customType = new CustomType(GetInterfaceName(values), values.Module); - public ClassTraverser(CodeClass codeClass, Action withProperty) - { - if (codeClass == null) - throw new ArgumentNullException("codeClass"); - - if (withProperty == null) - throw new ArgumentNullException("withProperty"); + typeContext.AddCustomType(codeClass.FullName, customType); + }); + }); - this.CodeClass = codeClass; - this.WithProperty = withProperty; + return typeContext; + } - if (codeClass.Members != null) - Traverse(codeClass.Members); - } + public IEnumerable GetAllInterfaces() + { + var typeContext = BuildContext(); + var byModuleName = new Dictionary(); + var tsMap = new Dictionary(); - private void Traverse(CodeElements members) - { - foreach (var property in members.OfType()) - WithProperty(property); - } -} -class NamespaceTraverser -{ - public Action WithCodeClass { get; private set; } + new ProjectTraverser(this.Project, (ns) => + { + new NamespaceTraverser(ns, (codeClass) => + { + if (codeClass.Attributes == null || codeClass.Attributes.Count == 0) + return; + + CodeAttribute attribute; + if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) + return; + + var values = GetInterfaceValues(codeClass, attribute); + + TypeScriptModule module; + if (!byModuleName.TryGetValue(values.Module, out module)) + { + module = new TypeScriptModule { QualifiedName = values.Module }; + byModuleName.Add(values.Module, module); + } + + var tsInterface = BuildInterface(codeClass, values, typeContext); + tsMap.Add(codeClass, tsInterface); + tsInterface.Module = module; + module.Interfaces.Add(tsInterface); + }); + }); - public NamespaceTraverser(CodeNamespace ns, Action withCodeClass) - { - if (ns == null) - throw new ArgumentNullException("ns"); - - if (withCodeClass == null) - throw new ArgumentNullException("withCodeClass"); - - WithCodeClass = withCodeClass; - - if (ns.Members != null) - Traverse(ns.Members); - } + var tsInterfaces = tsMap.Values.ToList(); + tsMap.Keys.ToList().ForEach(codeClass => + { + var parent = tsInterfaces.LastOrDefault(intf => codeClass.IsDerivedFrom[intf.FullName] && intf.FullName != codeClass.FullName); + if (parent != null) + tsMap[codeClass].Parent = parent; + }); - private void Traverse(CodeElements members) - { - foreach (var codeClass in members.OfType()) - WithCodeClass(codeClass); - } -} -class ProjectTraverser -{ - public Action WithNamespace { get; private set; } + return byModuleName.Values + .OrderBy(m => m.QualifiedName) + .ToList(); + } + + private string GetInterfaceName(TypeScriptInterfaceAttributeValues attributeValues) + { + if (!string.IsNullOrEmpty(attributeValues.NamePrefix)) + return attributeValues.NamePrefix + attributeValues.Name; - public ProjectTraverser(Project project, Action withNamespace) - { - if (project == null) - throw new ArgumentNullException("project"); - - if (withNamespace == null) - throw new ArgumentNullException("withNamespace"); + return attributeValues.Name; + } - WithNamespace = withNamespace; + private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) + { + var tsInterface = new TypeScriptInterface + { + FullName = codeClass.FullName, + Name = GetInterfaceName(attributeValues) + }; - if (project.ProjectItems != null) - Traverse(project.ProjectItems); - } + TypescriptType indexedType; + if (TryGetIndexedType(codeClass, typeContext, out indexedType)) + tsInterface.IndexedType = indexedType; - private void Traverse(ProjectItems items) - { - foreach (ProjectItem pi in items) + new ClassTraverser(codeClass, (property) => + { + TypeScriptInterfaceMember member; + if (TryGetMember(property, typeContext, out member)) + tsInterface.Members.Add(member); + }); + return tsInterface; + } + + private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) { - if (pi.FileCodeModel != null) + foreach (CodeAttribute attr in attributes) { - var codeElements = pi.FileCodeModel.CodeElements; - foreach (var ns in codeElements.OfType()) - WithNamespace(ns); + if (attr.FullName == attributeFullName) + { + attribute = attr; + return true; + } } - if (pi.ProjectItems != null) - Traverse(pi.ProjectItems); + attribute = null; + return false; } - } -} -// -- Types ---------------------------------------------------------------------------------- - -class TypescriptType -{ - public virtual string Name { get { return "any"; } } + private bool TryGetIndexedType(CodeClass codeClass, TypeContext typeContext, out TypescriptType indexedType) + { + indexedType = null; + if (codeClass.Bases == null || codeClass.Bases.Count == 0) + return false; - public override string ToString() - { - return Name; - } -} -class StringType: TypescriptType -{ - public override string Name - { - get { return "string"; } - } -} -class NumberType : TypescriptType -{ - public override string Name - { - get { return "number"; } - } -} -class BoolType: TypescriptType -{ - public override string Name - { - get { return "bool"; } - } -} -class ArrayType: TypescriptType -{ - public TypescriptType ElementType { get; set; } + foreach (CodeElement baseClass in codeClass.Bases) + { + if (typeContext.IsGenericEnumerable(baseClass.FullName)) + { + string fullName = typeContext.UnwrapGenericType(baseClass.FullName); + indexedType = typeContext.GetTypeScriptType(fullName); + return true; + } + } - public override string ToString() - { - return ElementType.ToString() + "[]"; - } -} -class CustomType: TypescriptType -{ - private string m_name; + return false; + } - public override string Name - { - get { return m_name; } - } - - public string QualifedModule { get; private set; } + private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + { + var values = GetAttributeValues(interfaceAttribute); - public CustomType(string name, string qualifiedModule=null) - { - m_name = name; - this.QualifedModule = qualifiedModule; - } + return new TypeScriptInterfaceAttributeValues + { + Name = values.ContainsKey("Name") ? values["Name"] : codeClass.Name, + Module = values.ContainsKey("Module") ? values["Module"] : Settings.DefaultModule ?? "T4TS", + NamePrefix = values.ContainsKey("NamePrefix") ? values["NamePrefix"] : Settings.DefaultInterfaceNamePrefix ?? string.Empty + }; + } - public override string ToString() - { - if (string.IsNullOrWhiteSpace(QualifedModule)) - return base.ToString(); + private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + { + member = null; + if (property.Access != vsCMAccess.vsCMAccessPublic) + return false; - return QualifedModule + "." + base.ToString(); - } -} -class TypeContext -{ - private static readonly string[] genericCollectionTypeStarts = new string[] { - "System.Collections.Generic.List<", - "System.Collections.Generic.IList<", - "System.Collections.Generic.ICollection<" - }; + var getter = property.Getter; + if (getter == null) + return false; - /// - /// Lookup table for "custom types", ie. non-builtin types. Keyed on the FullName of the type. - /// - private Dictionary customTypes = new Dictionary(); + var values = GetMemberValues(property, typeContext); + member = new TypeScriptInterfaceMember + { + Name = values.Name ?? property.Name, + FullName = property.FullName, + Optional = values.Optional, + Type = (string.IsNullOrWhiteSpace(values.Type)) + ? typeContext.GetTypeScriptType(getter.Type) + : new CustomType(values.Type) + }; - public void AddCustomType(string typeFullName, CustomType customType) - { - customTypes.Add(typeFullName, customType); - } + if (values.CamelCase && values.Name == null) + member.Name = member.Name.Substring(0, 1).ToLowerInvariant() + member.Name.Substring(1); - public bool TryGetCustomType(string typeFullName, out CustomType customType) - { - return customTypes.TryGetValue(typeFullName, out customType); - } + return true; + } - public TypescriptType GetTypeScriptType(CodeTypeRef codeType) - { - switch (codeType.TypeKind) + private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) { - case vsCMTypeRef.vsCMTypeRefChar: - case vsCMTypeRef.vsCMTypeRefString: - return new StringType(); + bool? attributeOptional = null; + bool? attributeCamelCase = null; + string attributeName = null; + string attributeType = null; - case vsCMTypeRef.vsCMTypeRefBool: - return new BoolType(); + CodeAttribute attribute; + if (TryGetAttribute(property.Attributes, MemberAttributeFullName, out attribute)) + { + var values = GetAttributeValues(attribute); + if (values.ContainsKey("Optional")) + attributeOptional = values["Optional"] == "true"; - case vsCMTypeRef.vsCMTypeRefByte: - case vsCMTypeRef.vsCMTypeRefDouble: - case vsCMTypeRef.vsCMTypeRefInt: - case vsCMTypeRef.vsCMTypeRefShort: - case vsCMTypeRef.vsCMTypeRefFloat: - case vsCMTypeRef.vsCMTypeRefLong: - case vsCMTypeRef.vsCMTypeRefDecimal: - return new NumberType(); + if (values.ContainsKey("CamelCase")) + attributeCamelCase = values["CamelCase"] == "true"; - default: - return TryResolveType(codeType); - } - } + values.TryGetValue("Name", out attributeName); + values.TryGetValue("Type", out attributeType); + } - private TypescriptType TryResolveType(CodeTypeRef codeType) - { - if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) - { - return new ArrayType() + return new TypeScriptMemberAttributeValues { - ElementType = GetTypeScriptType(codeType.ElementType) + Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, + Name = attributeName, + Type = attributeType, + CamelCase = attributeCamelCase ?? Settings.DefaultCamelCaseMemberNames }; } - return GetTypeScriptType(codeType.AsFullName); - } + private Dictionary GetAttributeValues(CodeAttribute codeAttribute) + { + var values = new Dictionary(); + foreach (CodeElement child in codeAttribute.Children) + { + var property = (EnvDTE80.CodeAttributeArgument)child; + if (property == null || property.Value == null) + continue; + + // remove quotes if the property is a string + string val = property.Value ?? string.Empty; + if (val.StartsWith("\"") && val.EndsWith("\"")) + val = val.Substring(1, val.Length - 2); - private ArrayType TryResolveEnumerableType(string typeFullName) + values.Add(property.Name, val); + } + + return values; + } + } + public class TypeContext { - return new ArrayType - { - ElementType = GetTypeScriptType(typeFullName) + private static readonly string[] genericCollectionTypeStarts = new string[] { + "System.Collections.Generic.List<", + "System.Collections.Generic.IList<", + "System.Collections.Generic.ICollection<" }; - } - public TypescriptType GetTypeScriptType(string typeFullName) - { - CustomType customType; - if (customTypes.TryGetValue(typeFullName, out customType)) - return customType; + private static readonly string nullableTypeStart = "System.Nullable<"; + + /// + /// Lookup table for "custom types", ie. non-builtin types. Keyed on the FullName of the type. + /// + private Dictionary customTypes = new Dictionary(); + + public void AddCustomType(string typeFullName, CustomType customType) + { + customTypes.Add(typeFullName, customType); + } + + public bool TryGetCustomType(string typeFullName, out CustomType customType) + { + return customTypes.TryGetValue(typeFullName, out customType); + } + + public TypescriptType GetTypeScriptType(CodeTypeRef codeType) + { + switch (codeType.TypeKind) + { + case vsCMTypeRef.vsCMTypeRefChar: + case vsCMTypeRef.vsCMTypeRefString: + return new StringType(); + + case vsCMTypeRef.vsCMTypeRefBool: + return new BoolType(); + + case vsCMTypeRef.vsCMTypeRefByte: + case vsCMTypeRef.vsCMTypeRefDouble: + case vsCMTypeRef.vsCMTypeRefInt: + case vsCMTypeRef.vsCMTypeRefShort: + case vsCMTypeRef.vsCMTypeRefFloat: + case vsCMTypeRef.vsCMTypeRefLong: + case vsCMTypeRef.vsCMTypeRefDecimal: + return new NumberType(); + + default: + return TryResolveType(codeType); + } + } + + private TypescriptType TryResolveType(CodeTypeRef codeType) + { + if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) + { + return new ArrayType() + { + ElementType = GetTypeScriptType(codeType.ElementType) + }; + } - if (IsGenericEnumerable(typeFullName)) + return GetTypeScriptType(codeType.AsFullName); + } + + private ArrayType TryResolveEnumerableType(string typeFullName) { return new ArrayType { - ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + ElementType = GetTypeScriptType(typeFullName) }; } - switch (typeFullName) + public TypescriptType GetTypeScriptType(string typeFullName) { - case "System.Double": - case "System.Int16": - case "System.Int32": - case "System.Int64": - case "System.UInt16": - case "System.UInt32": - case "System.UInt64": - case "System.Decimal": - case "System.Byte": - case "System.SByte": - case "System.Single": - return new NumberType(); + CustomType customType; + if (customTypes.TryGetValue(typeFullName, out customType)) + return customType; - case "System.String": - case "System.DateTime": - return new StringType(); + if (IsGenericEnumerable(typeFullName)) + { + return new ArrayType + { + ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } + else if (IsNullable(typeFullName)) + { + return new NullableType + { + WrappedType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } - default: - return new TypescriptType(); + switch (typeFullName) + { + case "System.Double": + case "System.Int16": + case "System.Int32": + case "System.Int64": + case "System.UInt16": + case "System.UInt32": + case "System.UInt64": + case "System.Decimal": + case "System.Byte": + case "System.SByte": + case "System.Single": + return new NumberType(); + + case "System.String": + case "System.DateTime": + return new StringType(); + + default: + return new TypescriptType(); + } } - } - public string UnwrapGenericType(string typeFullName) - { - int firstIndex = typeFullName.IndexOf('<'); - return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); - } + private bool IsNullable(string typeFullName) + { + return typeFullName.StartsWith(nullableTypeStart); + } - public bool IsGenericEnumerable(string typeFullName) - { - return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); + public string UnwrapGenericType(string typeFullName) + { + int firstIndex = typeFullName.IndexOf('<'); + return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); + } + + public bool IsGenericEnumerable(string typeFullName) + { + return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); + } } -} + public class InterfaceOutputAppender : OutputAppender + { + private bool InGlobalModule { get; set; } -// -- Code generation -------------------------------------------------------------------------- + public InterfaceOutputAppender(StringBuilder output, int baseIndentation, bool inGlobalModule) + : base(output, baseIndentation) + { + this.InGlobalModule = inGlobalModule; + } -class CodeGenerator -{ - public Project Project { get; private set; } - public Settings Settings { get; private set; } + public override void AppendOutput(TypeScriptInterface tsInterface) + { + BeginInterface(tsInterface); - private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; - private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; + AppendMembers(tsInterface); + + if (tsInterface.IndexedType != null) + AppendIndexer(tsInterface); - public CodeGenerator(Project project, Settings settings) - { - if (project == null) - throw new ArgumentNullException("project"); + EndInterface(); + } - if (settings == null) - throw new ArgumentNullException("settings"); + private void AppendMembers(TypeScriptInterface tsInterface) + { + var appender = new MemberOutputAppender(Output, BaseIndentation + 4); + foreach (var member in tsInterface.Members) + appender.AppendOutput(member); + } - this.Project = project; - this.Settings = settings; - } + private void BeginInterface(TypeScriptInterface tsInterface) + { + AppendIndentedLine("/** Generated from " + tsInterface.FullName + " **/"); - public TypeContext BuildContext() - { - var typeContext = new TypeContext(); + if (InGlobalModule) + AppendIndented("interface " + tsInterface.Name); + else + AppendIndented("export interface " + tsInterface.Name); + + if (tsInterface.Parent != null) + Output.Append(" extends " + (tsInterface.Parent.Module.IsGlobal ? "" : tsInterface.Parent.Module.QualifiedName + ".") + tsInterface.Parent.Name); + + Output.AppendLine(" {"); + } - new ProjectTraverser(this.Project, (ns) => + private void EndInterface() { - new NamespaceTraverser(ns, (codeClass) => - { - CodeAttribute attribute; - if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) - return; + AppendIndentedLine("}"); + } - var values = GetInterfaceValues(codeClass, attribute); - var customType = new CustomType(values.Name, values.Module); + private void AppendIndexer(TypeScriptInterface tsInterface) + { + AppendIndendation(); + Output.AppendFormat(" [index: number]: {0};", tsInterface.IndexedType); + Output.AppendLine(); + } + } + public class MemberOutputAppender : OutputAppender + { + public MemberOutputAppender(StringBuilder output, int baseIndentation) + : base(output, baseIndentation) + { + } - typeContext.AddCustomType(codeClass.FullName, customType); - }); - }); + public override void AppendOutput(TypeScriptInterfaceMember member) + { + AppendIndendation(); - return typeContext; - } + bool isOptional = member.Optional || (member.Type is NullableType); - public IEnumerable GetAllInterfaces() + Output.AppendFormat("{0}{1}: {2}", + member.Name, + (isOptional ? "?" : ""), + member.Type + ); + + Output.AppendLine(";"); + } + } + public class ModuleOutputAppender : OutputAppender { - var typeContext = BuildContext(); - var byModuleName = new Dictionary(); - var tsMap = new Dictionary(); + public ModuleOutputAppender(StringBuilder output, int baseIndentation) + : base(output, baseIndentation) + { + } - new ProjectTraverser(this.Project, (ns) => + public override void AppendOutput(TypeScriptModule module) { - new NamespaceTraverser(ns, (codeClass) => - { - if (codeClass.Attributes == null || codeClass.Attributes.Count == 0) - return; + BeginModule(module); - CodeAttribute attribute; - if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) - return; + var interfaceAppender = new InterfaceOutputAppender(Output, BaseIndentation + 4, module.IsGlobal); + foreach (var tsInterface in module.Interfaces) + interfaceAppender.AppendOutput(tsInterface); - var values = GetInterfaceValues(codeClass, attribute); + EndModule(module); + } - TypeScriptModule module; - if (!byModuleName.TryGetValue(values.Module, out module)) - { - module = new TypeScriptModule { QualifiedName = values.Module }; - byModuleName.Add(values.Module, module); - } + private void BeginModule(TypeScriptModule module) + { + if (module.IsGlobal) + { + Output.AppendLine("// -- Begin global interfaces"); + } + else + { + Output.Append("module "); + Output.Append(module.QualifiedName); + Output.AppendLine(" {"); + } + } - var tsInterface = BuildInterface(codeClass, values, typeContext); - tsMap.Add(codeClass, tsInterface); - tsInterface.Module = module; - module.Interfaces.Add(tsInterface); - }); - }); - - var tsInterfaces = tsMap.Values.ToList(); - tsMap.Keys.ToList().ForEach(codeClass => - { - var parent = tsInterfaces.LastOrDefault(intf => codeClass.IsDerivedFrom[intf.FullName] && intf.FullName != codeClass.FullName); - if (parent != null) - tsMap[codeClass].Parent = parent; - }); - - return byModuleName.Values - .OrderBy(m => m.QualifiedName) - .ToList(); + private void EndModule(TypeScriptModule module) + { + if (module.IsGlobal) + Output.AppendLine("// -- End global interfaces"); + else + Output.AppendLine("}"); + } } - - private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) + public abstract class OutputAppender where TSegment: class { - var tsInterface = new TypeScriptInterface + protected StringBuilder Output { get; private set; } + protected int BaseIndentation { get; private set; } + + public OutputAppender(StringBuilder output, int baseIndentation) { - FullName = codeClass.FullName, - Name = attributeValues.Name - }; + if (output == null) + throw new ArgumentNullException("output"); + + this.Output = output; + this.BaseIndentation = baseIndentation; + } - TypescriptType indexedType; - if (TryGetIndexedType(codeClass, typeContext, out indexedType)) - tsInterface.IndexedType = indexedType; + public abstract void AppendOutput(TSegment segment); - new ClassTraverser(codeClass, (property) => + protected void AppendIndented(string text) { - TypeScriptInterfaceMember member; - if (TryGetMember(property, typeContext, out member)) - tsInterface.Members.Add(member); - }); - return tsInterface; - } + AppendIndendation(); + Output.Append(text); + } + + protected void AppendIndentedLine(string line) + { + AppendIndendation(); + Output.AppendLine(line); + } - private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) + protected void AppendIndendation() + { + Output.Append(' ', BaseIndentation); + } + + public override string ToString() + { + return Output.ToString(); + } + } + public static class OutputFormatter { - foreach (CodeAttribute attr in attributes) + public static string GetOutput(List modules) { - if (attr.FullName == attributeFullName) + var output = new StringBuilder(); + + output.AppendLine("/****************************************************************************"); + output.AppendLine(" Generated by T4TS.tt - don't make any changes in this file"); + output.AppendLine("****************************************************************************/"); + + var moduleAppender = new ModuleOutputAppender(output, 0); + foreach (var module in modules) { - attribute = attr; - return true; + output.AppendLine(); + moduleAppender.AppendOutput(module); } + + return output.ToString(); } + } + public class Settings + { + /// + /// The default module of the generated interface, if not specified by the TypeScriptInterfaceAttribute + /// + public string DefaultModule { get; set; } + + /// + /// The default value for Optional, if not specified by the TypeScriptMemberAttribute + /// + public bool DefaultOptional { get; set; } + + /// + /// The default value for the CamelCase flag for an interface member name, if not specified by the TypeScriptMemberAttribute + /// + public bool DefaultCamelCaseMemberNames { get; set; } - attribute = null; - return false; + /// + /// The default string to prefix interface names with. For instance, you might want to prefix the names with an "I" to get conventional interface names. + /// + public string DefaultInterfaceNamePrefix { get; set; } } + public class TypeScriptInterface + { + public string Name { get; set; } + public string FullName { get; set; } + + public List Members { get; set; } + public TypescriptType IndexedType { get; set; } + public TypeScriptInterface Parent { get; set; } + public TypeScriptModule Module { get; set; } - private bool TryGetIndexedType(CodeClass codeClass, TypeContext typeContext, out TypescriptType indexedType) + public TypeScriptInterface() + { + Members = new List(); + } + } + public class TypeScriptInterfaceAttributeValues { - indexedType = null; - if (codeClass.Bases == null || codeClass.Bases.Count == 0) - return false; + public string Module { get; set; } + public string Name { get; set; } + public string NamePrefix { get; set; } + } + public class TypeScriptInterfaceMember + { + public string Name { get; set; } + public TypescriptType Type { get; set; } + public bool Optional { get; set; } + public string FullName { get; set; } + } + public class TypeScriptMemberAttributeValues + { + public string Name { get; set; } + public bool Optional { get; set; } + public string Type { get; set; } + public bool CamelCase { get; set; } + } + public class TypeScriptModule + { + public string QualifiedName { get; set; } + public List Interfaces { get; set; } - foreach (CodeElement baseClass in codeClass.Bases) + /// + /// Returns true if this is the global namespace (ie. no module name) + /// + public bool IsGlobal { - if (typeContext.IsGenericEnumerable(baseClass.FullName)) - { - string fullName = typeContext.UnwrapGenericType(baseClass.FullName); - indexedType = typeContext.GetTypeScriptType(fullName); - return true; - } + get { return string.IsNullOrWhiteSpace(QualifiedName); } } - return false; + public TypeScriptModule() + { + Interfaces = new List(); + } } - - private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + public class ClassTraverser { - var values = GetAttributeValues(interfaceAttribute); + public CodeClass CodeClass { get; private set; } + public Action WithProperty { get; set; } - return new TypeScriptInterfaceAttributeValues + public ClassTraverser(CodeClass codeClass, Action withProperty) { - Name = values.ContainsKey("Name") ? values["Name"] : codeClass.Name, - Module = values.ContainsKey("Module") ? values["Module"] : Settings.DefaultModule ?? "T4TS", - }; + if (codeClass == null) + throw new ArgumentNullException("codeClass"); + + if (withProperty == null) + throw new ArgumentNullException("withProperty"); + + this.CodeClass = codeClass; + this.WithProperty = withProperty; + + if (codeClass.Members != null) + Traverse(codeClass.Members); + } + + private void Traverse(CodeElements members) + { + foreach (var property in members.OfType()) + WithProperty(property); + } } + public class NamespaceTraverser + { + public Action WithCodeClass { get; private set; } - private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + public NamespaceTraverser(CodeNamespace ns, Action withCodeClass) + { + if (ns == null) + throw new ArgumentNullException("ns"); + + if (withCodeClass == null) + throw new ArgumentNullException("withCodeClass"); + + WithCodeClass = withCodeClass; + + if (ns.Members != null) + Traverse(ns.Members); + } + + private void Traverse(CodeElements members) + { + foreach (var codeClass in members.OfType()) + WithCodeClass(codeClass); + } + } + public class ProjectTraverser { - member = null; - if (property.Access != vsCMAccess.vsCMAccessPublic) - return false; + public Action WithNamespace { get; private set; } - var getter = property.Getter; - if (getter == null) - return false; + public ProjectTraverser(Project project, Action withNamespace) + { + if (project == null) + throw new ArgumentNullException("project"); + + if (withNamespace == null) + throw new ArgumentNullException("withNamespace"); - var values = GetMemberValues(property, typeContext); - member = new TypeScriptInterfaceMember + WithNamespace = withNamespace; + + if (project.ProjectItems != null) + Traverse(project.ProjectItems); + } + + private void Traverse(ProjectItems items) { - Name = values.Name ?? property.Name, - FullName = property.FullName, - Optional = values.Optional, - Type = (string.IsNullOrWhiteSpace(values.Type)) - ? typeContext.GetTypeScriptType(getter.Type) - : new CustomType(values.Type) - }; + foreach (ProjectItem pi in items) + { + if (pi.FileCodeModel != null) + { + var codeElements = pi.FileCodeModel.CodeElements; + foreach (var ns in codeElements.OfType()) + WithNamespace(ns); + } - return true; + if (pi.ProjectItems != null) + Traverse(pi.ProjectItems); + } + } } + public class ArrayType: TypescriptType + { + public TypescriptType ElementType { get; set; } - private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) + public override string ToString() + { + return ElementType.ToString() + "[]"; + } + } + public class BoolType: TypescriptType { - bool? attributeOptional = null; - string attributeName = null; - string attributeType = null; + public override string Name + { + get { return "bool"; } + } + } + public class CustomType: TypescriptType + { + private string m_name; - CodeAttribute attribute; - if (TryGetAttribute(property.Attributes, MemberAttributeFullName, out attribute)) + public override string Name { - var values = GetAttributeValues(attribute); - if (values.ContainsKey("Optional")) - attributeOptional = values["Optional"] == "true"; + get { return m_name; } + } + + public string QualifedModule { get; private set; } - values.TryGetValue("Name", out attributeName); - values.TryGetValue("Type", out attributeType); + public CustomType(string name, string qualifiedModule=null) + { + m_name = name; + this.QualifedModule = qualifiedModule; } - return new TypeScriptMemberAttributeValues + public override string ToString() { - Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, - Name = attributeName, - Type = attributeType - }; + if (string.IsNullOrWhiteSpace(QualifedModule)) + return base.ToString(); + + return QualifedModule + "." + base.ToString(); + } } + public class NullableType : TypescriptType + { + public TypescriptType WrappedType { get; set; } - private Dictionary GetAttributeValues(CodeAttribute codeAttribute) + public override string ToString() + { + return WrappedType.ToString(); + } + } + public class NumberType : TypescriptType { - var values = new Dictionary(); - foreach (CodeElement child in codeAttribute.Children) + public override string Name { - var property = (EnvDTE80.CodeAttributeArgument)child; - if (property == null || property.Value == null) - continue; - - // remove quotes if the property is a string - string val = property.Value ?? string.Empty; - if (val.StartsWith("\"") && val.EndsWith("\"")) - val = val.Substring(1, val.Length - 2); - - values.Add(property.Name, val); + get { return "number"; } } + } + public class StringType: TypescriptType + { + public override string Name + { + get { return "string"; } + } + } + public class TypescriptType + { + public virtual string Name { get { return "any"; } } - return values; + public override string ToString() + { + return Name; + } } -} #> \ No newline at end of file diff --git a/build/T4TS.tt.settings.t4 b/build/T4TS.tt.settings.t4 index 43965d5..d3f697d 100644 --- a/build/T4TS.tt.settings.t4 +++ b/build/T4TS.tt.settings.t4 @@ -15,4 +15,13 @@ const string DefaultModule = "T4TS"; // generated member will look like "member?: type" instead of "member: type". const bool DefaultOptional = false; +// The default value for the CamelCase flag for an interface member name. +// If set to true, the first character of member names will be lower cased. +const bool DefaultCamelCaseMemberNames = false; + +// The default string to prefix interface names with. For instance, you +// might want to prefix the names with an "I" to get conventional +// interface names. +const string DefaultInterfaceNamePrefix = ""; + #> \ No newline at end of file