diff --git a/T4TS.Build/Models.cs b/T4TS.Build/Models.cs deleted file mode 100644 index aa5515d..0000000 --- a/T4TS.Build/Models.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace T4TS.Build -{ - [TypeScriptInterface(Module = "")] - public class Barfoo - { - public int Number { get; set; } - public Inherited Complex { get; set; } - public string Name { get; set; } - public DateTime DateTime { get; set; } - } - - [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")] - public class Inherited : List - { - [TypeScriptMember(Optional = true, Name = "OtherName")] - public string StringProperty { get; set; } - public int[] Integers { get; set; } - public List Doubles { get; set; } - public List> TwoDimList { get; set; } - } - - #region Inheritance Test - - public class TestClass - { - public string Property { get; set; } - } - - [TypeScriptInterface] - public class InheritanceTest1 : Barfoo - { - public string SomeString { get; set; } - public Foobar Recursive { get; set; } - } - - [TypeScriptInterface] - public class InheritanceTest2 : InheritanceTest1 - { - public string SomeString2 { get; set; } - public Foobar Recursive2 { get; set; } - } - - [TypeScriptInterface] - public class InheritanceTest3 : Inherited - { - public string SomeString3 { get; set; } - public Foobar Recursive3 { get; set; } - } - - [TypeScriptInterface] - public class InheritanceTest4 : TestClass - { - public string SomeString4 { get; set; } - public Foobar Recursive4 { get; set; } - } - - #endregion -} diff --git a/T4TS.Build/T4TS.Build.csproj b/T4TS.Build/T4TS.Build.csproj index cc0ee0e..15e7dea 100644 --- a/T4TS.Build/T4TS.Build.csproj +++ b/T4TS.Build/T4TS.Build.csproj @@ -47,7 +47,36 @@ - + + Models\Barfoo.cs + + + Models\Foobar.cs + + + Models\InheritanceTest1.cs + + + Models\InheritanceTest2.cs + + + Models\InheritanceTest3.cs + + + Models\InheritanceTest4.cs + + + Models\Inherited.cs + + + Models\Partial.First.cs + + + Models\Partial.Second.cs + + + Models\TestClass.cs + diff --git a/T4TS.Build/T4TS.d.ts b/T4TS.Build/T4TS.d.ts index 5a2adc2..e15c149 100644 --- a/T4TS.Build/T4TS.d.ts +++ b/T4TS.Build/T4TS.d.ts @@ -3,7 +3,7 @@ ****************************************************************************/ // -- Begin global interfaces - /** Generated from T4TS.Build.Barfoo **/ + /** Generated from T4TS.Example.Models.Barfoo **/ interface Barfoo { Number: number; Complex: T4TS.OverridenName; @@ -13,7 +13,7 @@ // -- End global interfaces module Fooz { - /** Generated from T4TS.Build.Foobar **/ + /** Generated from T4TS.Example.Models.Foobar **/ export interface IFoobar { OverrideAll?: bool; Recursive: Fooz.IFoobar; @@ -28,32 +28,41 @@ module Fooz { } module T4TS { - /** Generated from T4TS.Build.Inherited **/ - export interface OverridenName { - OtherName?: string; - Integers: number[]; - Doubles: number[]; - TwoDimList: number[][]; - [index: number]: Barfoo; - } - /** Generated from T4TS.Build.InheritanceTest1 **/ + /** Generated from T4TS.Example.Models.InheritanceTest1 **/ export interface InheritanceTest1 extends Barfoo { SomeString: string; Recursive: Fooz.IFoobar; } - /** Generated from T4TS.Build.InheritanceTest2 **/ + /** Generated from T4TS.Example.Models.InheritanceTest2 **/ export interface InheritanceTest2 extends T4TS.InheritanceTest1 { SomeString2: string; Recursive2: Fooz.IFoobar; } - /** Generated from T4TS.Build.InheritanceTest3 **/ + /** Generated from T4TS.Example.Models.InheritanceTest3 **/ export interface InheritanceTest3 extends T4TS.OverridenName { SomeString3: string; Recursive3: Fooz.IFoobar; } - /** Generated from T4TS.Build.InheritanceTest4 **/ + /** Generated from T4TS.Example.Models.InheritanceTest4 **/ export interface InheritanceTest4 { SomeString4: string; Recursive4: Fooz.IFoobar; } + /** Generated from T4TS.Example.Models.Inherited **/ + export interface OverridenName { + OtherName?: string; + Integers: number[]; + Doubles: number[]; + TwoDimList: number[][]; + [index: number]: Barfoo; + } + /** Generated from T4TS.Example.Models.Partial **/ + export interface Partial { + FromFirstClass: string; + } + /** Generated from T4TS.Example.Models.Partial **/ + export interface Partial { + FromSecondClass: string; + AlsoSecondClass?: any; + } } diff --git a/T4TS.Build/T4TS.tt b/T4TS.Build/T4TS.tt index 0f3543d..3b70ef3 100644 --- a/T4TS.Build/T4TS.tt +++ b/T4TS.Build/T4TS.tt @@ -62,784 +62,833 @@ Project GetProjectContainingT4File(DTE dte) { return projectItem.ContainingProject; } - public class CodeTraverser +public class InterfaceOutputAppender : OutputAppender { - public Project Project { get; private set; } - public Settings Settings { get; private set; } + private bool InGlobalModule { get; set; } - private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; - private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; + public InterfaceOutputAppender(StringBuilder output, int baseIndentation, bool inGlobalModule) + : base(output, baseIndentation) + { + this.InGlobalModule = inGlobalModule; + } - public CodeTraverser(Project project, Settings settings) + public override void AppendOutput(TypeScriptInterface tsInterface) { - if (project == null) - throw new ArgumentNullException("project"); + BeginInterface(tsInterface); - if (settings == null) - throw new ArgumentNullException("settings"); + AppendMembers(tsInterface); + + if (tsInterface.IndexedType != null) + AppendIndexer(tsInterface); - this.Project = project; - this.Settings = settings; + EndInterface(); } - public TypeContext BuildContext() + private void AppendMembers(TypeScriptInterface tsInterface) { - var typeContext = new TypeContext(); + var appender = new MemberOutputAppender(Output, BaseIndentation + 4); + foreach (var member in tsInterface.Members) + appender.AppendOutput(member); + } - new ProjectTraverser(this.Project, (ns) => - { - new NamespaceTraverser(ns, (codeClass) => - { - CodeAttribute attribute; - if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) - return; + private void BeginInterface(TypeScriptInterface tsInterface) + { + AppendIndentedLine("/** Generated from " + tsInterface.FullName + " **/"); - var values = GetInterfaceValues(codeClass, attribute); - var customType = new CustomType(GetInterfaceName(values), values.Module); + if (InGlobalModule) + AppendIndented("interface " + tsInterface.Name); + else + AppendIndented("export interface " + tsInterface.Name); - typeContext.AddCustomType(codeClass.FullName, customType); - }); - }); + if (tsInterface.Parent != null) + Output.Append(" extends " + (tsInterface.Parent.Module.IsGlobal ? "" : tsInterface.Parent.Module.QualifiedName + ".") + tsInterface.Parent.Name); - return typeContext; + Output.AppendLine(" {"); } - public IEnumerable GetAllInterfaces() + private void EndInterface() { - var typeContext = BuildContext(); - var byModuleName = new Dictionary(); - var tsMap = new Dictionary(); - - 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; + AppendIndentedLine("}"); + } - var values = GetInterfaceValues(codeClass, attribute); + private void AppendIndexer(TypeScriptInterface tsInterface) + { + AppendIndendation(); + Output.AppendFormat(" [index: number]: {0};", tsInterface.IndexedType); + Output.AppendLine(); + } + } - TypeScriptModule module; - if (!byModuleName.TryGetValue(values.Module, out module)) - { - module = new TypeScriptModule { QualifiedName = values.Module }; - byModuleName.Add(values.Module, module); - } + public class MemberOutputAppender : OutputAppender + { + public MemberOutputAppender(StringBuilder output, int baseIndentation) + : base(output, baseIndentation) + { + } - var tsInterface = BuildInterface(codeClass, values, typeContext); - tsMap.Add(codeClass, tsInterface); - tsInterface.Module = module; - module.Interfaces.Add(tsInterface); - }); - }); + public override void AppendOutput(TypeScriptInterfaceMember member) + { + AppendIndendation(); - 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; - }); + bool isOptional = member.Optional || (member.Type is NullableType); - return byModuleName.Values - .OrderBy(m => m.QualifiedName) - .ToList(); + Output.AppendFormat("{0}{1}: {2}", + member.Name, + (isOptional ? "?" : ""), + member.Type + ); + + Output.AppendLine(";"); } - - private string GetInterfaceName(TypeScriptInterfaceAttributeValues attributeValues) + } + + public class ModuleOutputAppender : OutputAppender + { + public ModuleOutputAppender(StringBuilder output, int baseIndentation) + : base(output, baseIndentation) { - if (!string.IsNullOrEmpty(attributeValues.NamePrefix)) - return attributeValues.NamePrefix + attributeValues.Name; - - return attributeValues.Name; } - private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) + public override void AppendOutput(TypeScriptModule module) { - var tsInterface = new TypeScriptInterface - { - FullName = codeClass.FullName, - Name = GetInterfaceName(attributeValues) - }; + BeginModule(module); - TypescriptType indexedType; - if (TryGetIndexedType(codeClass, typeContext, out indexedType)) - tsInterface.IndexedType = indexedType; + var interfaceAppender = new InterfaceOutputAppender(Output, BaseIndentation + 4, module.IsGlobal); + foreach (var tsInterface in module.Interfaces) + interfaceAppender.AppendOutput(tsInterface); - new ClassTraverser(codeClass, (property) => - { - TypeScriptInterfaceMember member; - if (TryGetMember(property, typeContext, out member)) - tsInterface.Members.Add(member); - }); - return tsInterface; + EndModule(module); } - private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) + private void BeginModule(TypeScriptModule module) { - foreach (CodeAttribute attr in attributes) + if (module.IsGlobal) { - if (attr.FullName == attributeFullName) - { - attribute = attr; - return true; - } + Output.AppendLine("// -- Begin global interfaces"); } - - attribute = null; - return false; - } - - private bool TryGetIndexedType(CodeClass codeClass, TypeContext typeContext, out TypescriptType indexedType) - { - indexedType = null; - if (codeClass.Bases == null || codeClass.Bases.Count == 0) - return false; - - foreach (CodeElement baseClass in codeClass.Bases) + else { - if (typeContext.IsGenericEnumerable(baseClass.FullName)) - { - string fullName = typeContext.UnwrapGenericType(baseClass.FullName); - indexedType = typeContext.GetTypeScriptType(fullName); - return true; - } + Output.Append("module "); + Output.Append(module.QualifiedName); + Output.AppendLine(" {"); } - - return false; } - private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + private void EndModule(TypeScriptModule module) { - var values = GetAttributeValues(interfaceAttribute); - - 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 - }; + if (module.IsGlobal) + Output.AppendLine("// -- End global interfaces"); + else + Output.AppendLine("}"); } + } + + public abstract class OutputAppender where TSegment: class + { + protected StringBuilder Output { get; private set; } + protected int BaseIndentation { get; private set; } - private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + public OutputAppender(StringBuilder output, int baseIndentation) { - member = null; - if (property.Access != vsCMAccess.vsCMAccessPublic) - return false; - - var getter = property.Getter; - if (getter == null) - return false; + if (output == null) + throw new ArgumentNullException("output"); - 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) - }; + this.Output = output; + this.BaseIndentation = baseIndentation; + } - if (values.CamelCase && values.Name == null) - member.Name = member.Name.Substring(0, 1).ToLowerInvariant() + member.Name.Substring(1); + public abstract void AppendOutput(TSegment segment); - return true; + protected void AppendIndented(string text) + { + AppendIndendation(); + Output.Append(text); } - private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) + protected void AppendIndentedLine(string line) { - bool? attributeOptional = null; - bool? attributeCamelCase = null; - string attributeName = null; - string attributeType = null; - - CodeAttribute attribute; - if (TryGetAttribute(property.Attributes, MemberAttributeFullName, out attribute)) - { - var values = GetAttributeValues(attribute); - if (values.ContainsKey("Optional")) - attributeOptional = values["Optional"] == "true"; + AppendIndendation(); + Output.AppendLine(line); + } - if (values.ContainsKey("CamelCase")) - attributeCamelCase = values["CamelCase"] == "true"; + protected void AppendIndendation() + { + Output.Append(' ', BaseIndentation); + } - values.TryGetValue("Name", out attributeName); - values.TryGetValue("Type", out attributeType); - } - - return new TypeScriptMemberAttributeValues - { - Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, - Name = attributeName, - Type = attributeType, - CamelCase = attributeCamelCase ?? Settings.DefaultCamelCaseMemberNames - }; + public override string ToString() + { + return Output.ToString(); } - - private Dictionary GetAttributeValues(CodeAttribute codeAttribute) + } + + public static class OutputFormatter + { + public static string GetOutput(List modules) { - 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); + var output = new StringBuilder(); + + output.AppendLine("/****************************************************************************"); + output.AppendLine(" Generated by T4TS.tt - don't make any changes in this file"); + output.AppendLine("****************************************************************************/"); - values.Add(property.Name, val); + var moduleAppender = new ModuleOutputAppender(output, 0); + foreach (var module in modules) + { + output.AppendLine(); + moduleAppender.AppendOutput(module); } - return values; + return output.ToString(); } } - public class TypeContext + + public class Settings { - private static readonly string[] genericCollectionTypeStarts = new string[] { - "System.Collections.Generic.List<", - "System.Collections.Generic.IList<", - "System.Collections.Generic.ICollection<" - }; + /// + /// The default module of the generated interface, if not specified by the TypeScriptInterfaceAttribute + /// + public string DefaultModule { get; set; } - private static readonly string nullableTypeStart = "System.Nullable<"; + /// + /// The default value for Optional, if not specified by the TypeScriptMemberAttribute + /// + public bool DefaultOptional { get; set; } /// - /// Lookup table for "custom types", ie. non-builtin types. Keyed on the FullName of the type. + /// The default value for the CamelCase flag for an interface member name, if not specified by the TypeScriptMemberAttribute /// - private Dictionary customTypes = new Dictionary(); + public bool DefaultCamelCaseMemberNames { get; set; } - public void AddCustomType(string typeFullName, CustomType customType) - { - customTypes.Add(typeFullName, customType); - } + /// + /// 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 bool TryGetCustomType(string typeFullName, out CustomType customType) - { - return customTypes.TryGetValue(typeFullName, out customType); - } + public List Members { get; set; } + public TypescriptType IndexedType { get; set; } + public TypeScriptInterface Parent { get; set; } + public TypeScriptModule Module { get; set; } - public TypescriptType GetTypeScriptType(CodeTypeRef codeType) + public TypeScriptInterface() { - 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); - } + Members = new List(); } + } + + public class TypeScriptInterfaceAttributeValues + { + 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; } + } - private TypescriptType TryResolveType(CodeTypeRef codeType) - { - if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) - { - return new ArrayType() - { - ElementType = GetTypeScriptType(codeType.ElementType) - }; - } + public class TypeScriptModule + { + public string QualifiedName { get; set; } + public List Interfaces { get; set; } - return GetTypeScriptType(codeType.AsFullName); + /// + /// Returns true if this is the global namespace (ie. no module name) + /// + public bool IsGlobal + { + get { return string.IsNullOrWhiteSpace(QualifiedName); } } - private ArrayType TryResolveEnumerableType(string typeFullName) + public TypeScriptModule() { - return new ArrayType - { - ElementType = GetTypeScriptType(typeFullName) - }; + Interfaces = new List(); } + } + + public class ClassTraverser + { + public CodeClass CodeClass { get; private set; } + public Action WithProperty { get; set; } - public TypescriptType GetTypeScriptType(string typeFullName) + public ClassTraverser(CodeClass codeClass, Action withProperty) { - CustomType customType; - if (customTypes.TryGetValue(typeFullName, out customType)) - return customType; - - if (IsGenericEnumerable(typeFullName)) - { - return new ArrayType - { - ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) - }; - } - else if (IsNullable(typeFullName)) - { - return new NullableType - { - WrappedType = GetTypeScriptType(UnwrapGenericType(typeFullName)) - }; - } - - 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(); + if (codeClass == null) + throw new ArgumentNullException("codeClass"); + + if (withProperty == null) + throw new ArgumentNullException("withProperty"); - case "System.String": - case "System.DateTime": - return new StringType(); + this.CodeClass = codeClass; + this.WithProperty = withProperty; - default: - return new TypescriptType(); - } + if (codeClass.Members != null) + Traverse(codeClass.Members); } - private bool IsNullable(string typeFullName) + private void Traverse(CodeElements members) { - return typeFullName.StartsWith(nullableTypeStart); + foreach (var property in members.OfType()) + WithProperty(property); } + } + + public class NamespaceTraverser + { + public Action WithCodeClass { get; private set; } - public string UnwrapGenericType(string typeFullName) + public NamespaceTraverser(CodeNamespace ns, Action withCodeClass) { - int firstIndex = typeFullName.IndexOf('<'); - return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); + if (ns == null) + throw new ArgumentNullException("ns"); + + if (withCodeClass == null) + throw new ArgumentNullException("withCodeClass"); + + WithCodeClass = withCodeClass; + + if (ns.Members != null) + Traverse(ns.Members); } - public bool IsGenericEnumerable(string typeFullName) + private void Traverse(CodeElements members) { - return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); + foreach (var codeClass in members.OfType()) + WithCodeClass(codeClass); } } - public class InterfaceOutputAppender : OutputAppender + + public class ProjectTraverser { - private bool InGlobalModule { get; set; } - - public InterfaceOutputAppender(StringBuilder output, int baseIndentation, bool inGlobalModule) - : base(output, baseIndentation) - { - this.InGlobalModule = inGlobalModule; - } + public Action WithNamespace { get; private set; } - public override void AppendOutput(TypeScriptInterface tsInterface) + public ProjectTraverser(Project project, Action withNamespace) { - BeginInterface(tsInterface); - - AppendMembers(tsInterface); + if (project == null) + throw new ArgumentNullException("project"); - if (tsInterface.IndexedType != null) - AppendIndexer(tsInterface); + if (withNamespace == null) + throw new ArgumentNullException("withNamespace"); - EndInterface(); + WithNamespace = withNamespace; + + if (project.ProjectItems != null) + Traverse(project.ProjectItems); } - private void AppendMembers(TypeScriptInterface tsInterface) + private void Traverse(ProjectItems items) { - var appender = new MemberOutputAppender(Output, BaseIndentation + 4); - foreach (var member in tsInterface.Members) - appender.AppendOutput(member); + foreach (ProjectItem pi in items) + { + if (pi.FileCodeModel != null) + { + var codeElements = pi.FileCodeModel.CodeElements; + foreach (var ns in codeElements.OfType()) + WithNamespace(ns); + } + + if (pi.ProjectItems != null) + Traverse(pi.ProjectItems); + } } + } + + public class ArrayType: TypescriptType + { + public TypescriptType ElementType { get; set; } - private void BeginInterface(TypeScriptInterface tsInterface) + public override string ToString() { - AppendIndentedLine("/** Generated from " + tsInterface.FullName + " **/"); + return ElementType.ToString() + "[]"; + } + } + + public class BoolType: TypescriptType + { + public override string Name + { + get { return "bool"; } + } + } + + public class InterfaceType : TypescriptType + { + public TypeScriptInterfaceAttributeValues AttributeValues { get; private set; } - 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(" {"); + public string QualifedModule + { + get + { + if (AttributeValues == null) + return null; + + return AttributeValues.Module; + } } - private void EndInterface() + public override string Name { - AppendIndentedLine("}"); + get + { + if (!string.IsNullOrEmpty(AttributeValues.NamePrefix)) + return AttributeValues.NamePrefix + AttributeValues.Name; + + return AttributeValues.Name; + } } - private void AppendIndexer(TypeScriptInterface tsInterface) + public InterfaceType(TypeScriptInterfaceAttributeValues values) { - AppendIndendation(); - Output.AppendFormat(" [index: number]: {0};", tsInterface.IndexedType); - Output.AppendLine(); + AttributeValues = values; } - } - public class MemberOutputAppender : OutputAppender - { - public MemberOutputAppender(StringBuilder output, int baseIndentation) - : base(output, baseIndentation) + + public InterfaceType(string name) { + AttributeValues = new TypeScriptInterfaceAttributeValues + { + Name = name + }; } - public override void AppendOutput(TypeScriptInterfaceMember member) + public override string ToString() { - AppendIndendation(); - - bool isOptional = member.Optional || (member.Type is NullableType); + if (string.IsNullOrWhiteSpace(QualifedModule)) + return base.ToString(); - Output.AppendFormat("{0}{1}: {2}", - member.Name, - (isOptional ? "?" : ""), - member.Type - ); - - Output.AppendLine(";"); + return QualifedModule + "." + base.ToString(); } } - public class ModuleOutputAppender : OutputAppender + + public class NullableType : TypescriptType { - public ModuleOutputAppender(StringBuilder output, int baseIndentation) - : base(output, baseIndentation) + public TypescriptType WrappedType { get; set; } + + public override string ToString() { + return WrappedType.ToString(); } - - public override void AppendOutput(TypeScriptModule module) + } + + public class NumberType : TypescriptType + { + public override string Name { - BeginModule(module); - - var interfaceAppender = new InterfaceOutputAppender(Output, BaseIndentation + 4, module.IsGlobal); - foreach (var tsInterface in module.Interfaces) - interfaceAppender.AppendOutput(tsInterface); - - EndModule(module); + get { return "number"; } } - - private void BeginModule(TypeScriptModule module) + } + + public class StringType: TypescriptType + { + public override string Name { - if (module.IsGlobal) - { - Output.AppendLine("// -- Begin global interfaces"); - } - else - { - Output.Append("module "); - Output.Append(module.QualifiedName); - Output.AppendLine(" {"); - } + get { return "string"; } } + } - private void EndModule(TypeScriptModule module) + public class TypescriptType + { + public virtual string Name { get { return "any"; } } + + public override string ToString() { - if (module.IsGlobal) - Output.AppendLine("// -- End global interfaces"); - else - Output.AppendLine("}"); + return Name; } } - public abstract class OutputAppender where TSegment: class + + public class CodeTraverser { - protected StringBuilder Output { get; private set; } - protected int BaseIndentation { get; private set; } + public Project Project { get; private set; } + public Settings Settings { get; private set; } - public OutputAppender(StringBuilder output, int baseIndentation) + private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; + private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; + + public CodeTraverser(Project project, Settings settings) { - if (output == null) - throw new ArgumentNullException("output"); + if (project == null) + throw new ArgumentNullException("project"); - this.Output = output; - this.BaseIndentation = baseIndentation; - } + if (settings == null) + throw new ArgumentNullException("settings"); - public abstract void AppendOutput(TSegment segment); + this.Project = project; + this.Settings = settings; + } - protected void AppendIndented(string text) + public TypeContext BuildContext() { - AppendIndendation(); - Output.Append(text); + var typeContext = new TypeContext(); + var partialClasses = new Dictionary(); + + new ProjectTraverser(this.Project, (ns) => + { + new NamespaceTraverser(ns, (codeClass) => + { + CodeAttribute attribute; + if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) + return; + + var values = GetInterfaceValues(codeClass, attribute); + var interfaceType = new InterfaceType(values); + + if (!typeContext.ContainsInterfaceType(codeClass.FullName)) + typeContext.AddInterfaceType(codeClass.FullName, interfaceType); + }); + }); + + return typeContext; } - protected void AppendIndentedLine(string line) + public IEnumerable GetAllInterfaces() { - AppendIndendation(); - Output.AppendLine(line); + var typeContext = BuildContext(); + var byModuleName = new Dictionary(); + var tsMap = new Dictionary(); + + new ProjectTraverser(this.Project, (ns) => + { + new NamespaceTraverser(ns, (codeClass) => + { + InterfaceType interfaceType; + if (!typeContext.TryGetInterfaceType(codeClass.FullName, out interfaceType)) + return; + + var values = interfaceType.AttributeValues; + + 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); + }); + }); + + 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 string GetInterfaceName(TypeScriptInterfaceAttributeValues attributeValues) + { + if (!string.IsNullOrEmpty(attributeValues.NamePrefix)) + return attributeValues.NamePrefix + attributeValues.Name; - protected void AppendIndendation() + return attributeValues.Name; + } + + private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) { - Output.Append(' ', BaseIndentation); + var tsInterface = new TypeScriptInterface + { + FullName = codeClass.FullName, + Name = GetInterfaceName(attributeValues) + }; + + TypescriptType indexedType; + if (TryGetIndexedType(codeClass, typeContext, out indexedType)) + tsInterface.IndexedType = indexedType; + + new ClassTraverser(codeClass, (property) => + { + TypeScriptInterfaceMember member; + if (TryGetMember(property, typeContext, out member)) + tsInterface.Members.Add(member); + }); + + return tsInterface; } - public override string ToString() + private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) { - return Output.ToString(); + foreach (CodeAttribute attr in attributes) + { + if (attr.FullName == attributeFullName) + { + attribute = attr; + return true; + } + } + + attribute = null; + return false; } - } - public static class OutputFormatter - { - public static string GetOutput(List modules) + + private bool TryGetIndexedType(CodeClass codeClass, TypeContext typeContext, out TypescriptType indexedType) { - var output = new StringBuilder(); - - output.AppendLine("/****************************************************************************"); - output.AppendLine(" Generated by T4TS.tt - don't make any changes in this file"); - output.AppendLine("****************************************************************************/"); + indexedType = null; + if (codeClass.Bases == null || codeClass.Bases.Count == 0) + return false; - var moduleAppender = new ModuleOutputAppender(output, 0); - foreach (var module in modules) + foreach (CodeElement baseClass in codeClass.Bases) { - output.AppendLine(); - moduleAppender.AppendOutput(module); + if (typeContext.IsGenericEnumerable(baseClass.FullName)) + { + string fullName = typeContext.UnwrapGenericType(baseClass.FullName); + indexedType = typeContext.GetTypeScriptType(fullName); + return true; + } } - return output.ToString(); + return false; } - } - 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; } + private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + { + var values = GetAttributeValues(interfaceAttribute); - /// - /// The default value for the CamelCase flag for an interface member name, if not specified by the TypeScriptMemberAttribute - /// - public bool DefaultCamelCaseMemberNames { get; set; } + 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 + }; + } - /// - /// 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; } + private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + { + member = null; + if (property.Access != vsCMAccess.vsCMAccessPublic) + return false; - public List Members { get; set; } - public TypescriptType IndexedType { get; set; } - public TypeScriptInterface Parent { get; set; } - public TypeScriptModule Module { get; set; } + var getter = property.Getter; + if (getter == null) + return false; - public TypeScriptInterface() - { - Members = new List(); - } - } - public class TypeScriptInterfaceAttributeValues - { - 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; } + 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 InterfaceType(values.Type) + }; - /// - /// Returns true if this is the global namespace (ie. no module name) - /// - public bool IsGlobal - { - get { return string.IsNullOrWhiteSpace(QualifiedName); } - } + if (values.CamelCase && values.Name == null) + member.Name = member.Name.Substring(0, 1).ToLowerInvariant() + member.Name.Substring(1); - public TypeScriptModule() - { - Interfaces = new List(); + return true; } - } - public class ClassTraverser - { - public CodeClass CodeClass { get; private set; } - public Action WithProperty { get; set; } - public ClassTraverser(CodeClass codeClass, Action withProperty) + private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) { - if (codeClass == null) - throw new ArgumentNullException("codeClass"); - - if (withProperty == null) - throw new ArgumentNullException("withProperty"); + bool? attributeOptional = null; + bool? attributeCamelCase = null; + string attributeName = null; + string attributeType = null; - this.CodeClass = codeClass; - this.WithProperty = withProperty; + CodeAttribute attribute; + if (TryGetAttribute(property.Attributes, MemberAttributeFullName, out attribute)) + { + var values = GetAttributeValues(attribute); + if (values.ContainsKey("Optional")) + attributeOptional = values["Optional"] == "true"; - if (codeClass.Members != null) - Traverse(codeClass.Members); + if (values.ContainsKey("CamelCase")) + attributeCamelCase = values["CamelCase"] == "true"; + + values.TryGetValue("Name", out attributeName); + values.TryGetValue("Type", out attributeType); + } + + return new TypeScriptMemberAttributeValues + { + Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, + Name = attributeName, + Type = attributeType, + CamelCase = attributeCamelCase ?? Settings.DefaultCamelCaseMemberNames + }; } - private void Traverse(CodeElements members) + private Dictionary GetAttributeValues(CodeAttribute codeAttribute) { - foreach (var property in members.OfType()) - WithProperty(property); + 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); + + values.Add(property.Name, val); + } + + return values; } } - public class NamespaceTraverser + + public class TypeContext { - public Action WithCodeClass { get; private set; } + private static readonly string[] genericCollectionTypeStarts = new string[] { + "System.Collections.Generic.List<", + "System.Collections.Generic.IList<", + "System.Collections.Generic.ICollection<" + }; - public NamespaceTraverser(CodeNamespace ns, Action withCodeClass) + private static readonly string nullableTypeStart = "System.Nullable<"; + + /// + /// Lookup table for "interface types", ie. non-builtin types (typically classes or unknown types). Keyed on the FullName of the type. + /// + private Dictionary interfaceTypes = new Dictionary(); + + public void AddInterfaceType(string typeFullName, InterfaceType interfaceType) { - if (ns == null) - throw new ArgumentNullException("ns"); - - if (withCodeClass == null) - throw new ArgumentNullException("withCodeClass"); - - WithCodeClass = withCodeClass; - - if (ns.Members != null) - Traverse(ns.Members); + interfaceTypes.Add(typeFullName, interfaceType); } - private void Traverse(CodeElements members) + public bool TryGetInterfaceType(string typeFullName, out InterfaceType interfaceType) { - foreach (var codeClass in members.OfType()) - WithCodeClass(codeClass); + return interfaceTypes.TryGetValue(typeFullName, out interfaceType); } - } - public class ProjectTraverser - { - public Action WithNamespace { get; private set; } - public ProjectTraverser(Project project, Action withNamespace) + public bool ContainsInterfaceType(string typeFullName) { - if (project == null) - throw new ArgumentNullException("project"); - - if (withNamespace == null) - throw new ArgumentNullException("withNamespace"); - - WithNamespace = withNamespace; - - if (project.ProjectItems != null) - Traverse(project.ProjectItems); + return interfaceTypes.ContainsKey(typeFullName); } - private void Traverse(ProjectItems items) + public TypescriptType GetTypeScriptType(CodeTypeRef codeType) { - foreach (ProjectItem pi in items) + switch (codeType.TypeKind) { - if (pi.FileCodeModel != null) - { - var codeElements = pi.FileCodeModel.CodeElements; - foreach (var ns in codeElements.OfType()) - WithNamespace(ns); - } + case vsCMTypeRef.vsCMTypeRefChar: + case vsCMTypeRef.vsCMTypeRefString: + return new StringType(); - if (pi.ProjectItems != null) - Traverse(pi.ProjectItems); + 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); } } - } - public class ArrayType: TypescriptType - { - public TypescriptType ElementType { get; set; } - public override string ToString() - { - return ElementType.ToString() + "[]"; - } - } - public class BoolType: TypescriptType - { - public override string Name + private TypescriptType TryResolveType(CodeTypeRef codeType) { - get { return "bool"; } - } - } - public class CustomType: TypescriptType - { - private string m_name; + if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) + { + return new ArrayType() + { + ElementType = GetTypeScriptType(codeType.ElementType) + }; + } - public override string Name - { - get { return m_name; } + return GetTypeScriptType(codeType.AsFullName); } - - public string QualifedModule { get; private set; } - public CustomType(string name, string qualifiedModule=null) + private ArrayType TryResolveEnumerableType(string typeFullName) { - m_name = name; - this.QualifedModule = qualifiedModule; + return new ArrayType + { + ElementType = GetTypeScriptType(typeFullName) + }; } - public override string ToString() + public TypescriptType GetTypeScriptType(string typeFullName) { - if (string.IsNullOrWhiteSpace(QualifedModule)) - return base.ToString(); + InterfaceType interfaceType; + if (interfaceTypes.TryGetValue(typeFullName, out interfaceType)) + return interfaceType; - return QualifedModule + "." + base.ToString(); - } - } - public class NullableType : TypescriptType - { - public TypescriptType WrappedType { get; set; } + if (IsGenericEnumerable(typeFullName)) + { + return new ArrayType + { + ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } + else if (IsNullable(typeFullName)) + { + return new NullableType + { + WrappedType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } - public override string ToString() - { - return WrappedType.ToString(); + 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 class NumberType : TypescriptType - { - public override string Name + + private bool IsNullable(string typeFullName) { - get { return "number"; } + return typeFullName.StartsWith(nullableTypeStart); } - } - public class StringType: TypescriptType - { - public override string Name + + public string UnwrapGenericType(string typeFullName) { - get { return "string"; } + int firstIndex = typeFullName.IndexOf('<'); + return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); } - } - public class TypescriptType - { - public virtual string Name { get { return "any"; } } - public override string ToString() + public bool IsGenericEnumerable(string typeFullName) { - return Name; + return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); } } #> \ No newline at end of file diff --git a/build/T4TS.Attributes.dll b/build/T4TS.Attributes.dll index 0e67b6d..d2f307e 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 0f3543d..3b70ef3 100644 --- a/build/T4TS.tt +++ b/build/T4TS.tt @@ -62,784 +62,833 @@ Project GetProjectContainingT4File(DTE dte) { return projectItem.ContainingProject; } - public class CodeTraverser +public class InterfaceOutputAppender : OutputAppender { - public Project Project { get; private set; } - public Settings Settings { get; private set; } + private bool InGlobalModule { get; set; } - private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; - private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; + public InterfaceOutputAppender(StringBuilder output, int baseIndentation, bool inGlobalModule) + : base(output, baseIndentation) + { + this.InGlobalModule = inGlobalModule; + } - public CodeTraverser(Project project, Settings settings) + public override void AppendOutput(TypeScriptInterface tsInterface) { - if (project == null) - throw new ArgumentNullException("project"); + BeginInterface(tsInterface); - if (settings == null) - throw new ArgumentNullException("settings"); + AppendMembers(tsInterface); + + if (tsInterface.IndexedType != null) + AppendIndexer(tsInterface); - this.Project = project; - this.Settings = settings; + EndInterface(); } - public TypeContext BuildContext() + private void AppendMembers(TypeScriptInterface tsInterface) { - var typeContext = new TypeContext(); + var appender = new MemberOutputAppender(Output, BaseIndentation + 4); + foreach (var member in tsInterface.Members) + appender.AppendOutput(member); + } - new ProjectTraverser(this.Project, (ns) => - { - new NamespaceTraverser(ns, (codeClass) => - { - CodeAttribute attribute; - if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) - return; + private void BeginInterface(TypeScriptInterface tsInterface) + { + AppendIndentedLine("/** Generated from " + tsInterface.FullName + " **/"); - var values = GetInterfaceValues(codeClass, attribute); - var customType = new CustomType(GetInterfaceName(values), values.Module); + if (InGlobalModule) + AppendIndented("interface " + tsInterface.Name); + else + AppendIndented("export interface " + tsInterface.Name); - typeContext.AddCustomType(codeClass.FullName, customType); - }); - }); + if (tsInterface.Parent != null) + Output.Append(" extends " + (tsInterface.Parent.Module.IsGlobal ? "" : tsInterface.Parent.Module.QualifiedName + ".") + tsInterface.Parent.Name); - return typeContext; + Output.AppendLine(" {"); } - public IEnumerable GetAllInterfaces() + private void EndInterface() { - var typeContext = BuildContext(); - var byModuleName = new Dictionary(); - var tsMap = new Dictionary(); - - 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; + AppendIndentedLine("}"); + } - var values = GetInterfaceValues(codeClass, attribute); + private void AppendIndexer(TypeScriptInterface tsInterface) + { + AppendIndendation(); + Output.AppendFormat(" [index: number]: {0};", tsInterface.IndexedType); + Output.AppendLine(); + } + } - TypeScriptModule module; - if (!byModuleName.TryGetValue(values.Module, out module)) - { - module = new TypeScriptModule { QualifiedName = values.Module }; - byModuleName.Add(values.Module, module); - } + public class MemberOutputAppender : OutputAppender + { + public MemberOutputAppender(StringBuilder output, int baseIndentation) + : base(output, baseIndentation) + { + } - var tsInterface = BuildInterface(codeClass, values, typeContext); - tsMap.Add(codeClass, tsInterface); - tsInterface.Module = module; - module.Interfaces.Add(tsInterface); - }); - }); + public override void AppendOutput(TypeScriptInterfaceMember member) + { + AppendIndendation(); - 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; - }); + bool isOptional = member.Optional || (member.Type is NullableType); - return byModuleName.Values - .OrderBy(m => m.QualifiedName) - .ToList(); + Output.AppendFormat("{0}{1}: {2}", + member.Name, + (isOptional ? "?" : ""), + member.Type + ); + + Output.AppendLine(";"); } - - private string GetInterfaceName(TypeScriptInterfaceAttributeValues attributeValues) + } + + public class ModuleOutputAppender : OutputAppender + { + public ModuleOutputAppender(StringBuilder output, int baseIndentation) + : base(output, baseIndentation) { - if (!string.IsNullOrEmpty(attributeValues.NamePrefix)) - return attributeValues.NamePrefix + attributeValues.Name; - - return attributeValues.Name; } - private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) + public override void AppendOutput(TypeScriptModule module) { - var tsInterface = new TypeScriptInterface - { - FullName = codeClass.FullName, - Name = GetInterfaceName(attributeValues) - }; + BeginModule(module); - TypescriptType indexedType; - if (TryGetIndexedType(codeClass, typeContext, out indexedType)) - tsInterface.IndexedType = indexedType; + var interfaceAppender = new InterfaceOutputAppender(Output, BaseIndentation + 4, module.IsGlobal); + foreach (var tsInterface in module.Interfaces) + interfaceAppender.AppendOutput(tsInterface); - new ClassTraverser(codeClass, (property) => - { - TypeScriptInterfaceMember member; - if (TryGetMember(property, typeContext, out member)) - tsInterface.Members.Add(member); - }); - return tsInterface; + EndModule(module); } - private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) + private void BeginModule(TypeScriptModule module) { - foreach (CodeAttribute attr in attributes) + if (module.IsGlobal) { - if (attr.FullName == attributeFullName) - { - attribute = attr; - return true; - } + Output.AppendLine("// -- Begin global interfaces"); } - - attribute = null; - return false; - } - - private bool TryGetIndexedType(CodeClass codeClass, TypeContext typeContext, out TypescriptType indexedType) - { - indexedType = null; - if (codeClass.Bases == null || codeClass.Bases.Count == 0) - return false; - - foreach (CodeElement baseClass in codeClass.Bases) + else { - if (typeContext.IsGenericEnumerable(baseClass.FullName)) - { - string fullName = typeContext.UnwrapGenericType(baseClass.FullName); - indexedType = typeContext.GetTypeScriptType(fullName); - return true; - } + Output.Append("module "); + Output.Append(module.QualifiedName); + Output.AppendLine(" {"); } - - return false; } - private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + private void EndModule(TypeScriptModule module) { - var values = GetAttributeValues(interfaceAttribute); - - 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 - }; + if (module.IsGlobal) + Output.AppendLine("// -- End global interfaces"); + else + Output.AppendLine("}"); } + } + + public abstract class OutputAppender where TSegment: class + { + protected StringBuilder Output { get; private set; } + protected int BaseIndentation { get; private set; } - private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + public OutputAppender(StringBuilder output, int baseIndentation) { - member = null; - if (property.Access != vsCMAccess.vsCMAccessPublic) - return false; - - var getter = property.Getter; - if (getter == null) - return false; + if (output == null) + throw new ArgumentNullException("output"); - 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) - }; + this.Output = output; + this.BaseIndentation = baseIndentation; + } - if (values.CamelCase && values.Name == null) - member.Name = member.Name.Substring(0, 1).ToLowerInvariant() + member.Name.Substring(1); + public abstract void AppendOutput(TSegment segment); - return true; + protected void AppendIndented(string text) + { + AppendIndendation(); + Output.Append(text); } - private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) + protected void AppendIndentedLine(string line) { - bool? attributeOptional = null; - bool? attributeCamelCase = null; - string attributeName = null; - string attributeType = null; - - CodeAttribute attribute; - if (TryGetAttribute(property.Attributes, MemberAttributeFullName, out attribute)) - { - var values = GetAttributeValues(attribute); - if (values.ContainsKey("Optional")) - attributeOptional = values["Optional"] == "true"; + AppendIndendation(); + Output.AppendLine(line); + } - if (values.ContainsKey("CamelCase")) - attributeCamelCase = values["CamelCase"] == "true"; + protected void AppendIndendation() + { + Output.Append(' ', BaseIndentation); + } - values.TryGetValue("Name", out attributeName); - values.TryGetValue("Type", out attributeType); - } - - return new TypeScriptMemberAttributeValues - { - Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, - Name = attributeName, - Type = attributeType, - CamelCase = attributeCamelCase ?? Settings.DefaultCamelCaseMemberNames - }; + public override string ToString() + { + return Output.ToString(); } - - private Dictionary GetAttributeValues(CodeAttribute codeAttribute) + } + + public static class OutputFormatter + { + public static string GetOutput(List modules) { - 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); + var output = new StringBuilder(); + + output.AppendLine("/****************************************************************************"); + output.AppendLine(" Generated by T4TS.tt - don't make any changes in this file"); + output.AppendLine("****************************************************************************/"); - values.Add(property.Name, val); + var moduleAppender = new ModuleOutputAppender(output, 0); + foreach (var module in modules) + { + output.AppendLine(); + moduleAppender.AppendOutput(module); } - return values; + return output.ToString(); } } - public class TypeContext + + public class Settings { - private static readonly string[] genericCollectionTypeStarts = new string[] { - "System.Collections.Generic.List<", - "System.Collections.Generic.IList<", - "System.Collections.Generic.ICollection<" - }; + /// + /// The default module of the generated interface, if not specified by the TypeScriptInterfaceAttribute + /// + public string DefaultModule { get; set; } - private static readonly string nullableTypeStart = "System.Nullable<"; + /// + /// The default value for Optional, if not specified by the TypeScriptMemberAttribute + /// + public bool DefaultOptional { get; set; } /// - /// Lookup table for "custom types", ie. non-builtin types. Keyed on the FullName of the type. + /// The default value for the CamelCase flag for an interface member name, if not specified by the TypeScriptMemberAttribute /// - private Dictionary customTypes = new Dictionary(); + public bool DefaultCamelCaseMemberNames { get; set; } - public void AddCustomType(string typeFullName, CustomType customType) - { - customTypes.Add(typeFullName, customType); - } + /// + /// 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 bool TryGetCustomType(string typeFullName, out CustomType customType) - { - return customTypes.TryGetValue(typeFullName, out customType); - } + public List Members { get; set; } + public TypescriptType IndexedType { get; set; } + public TypeScriptInterface Parent { get; set; } + public TypeScriptModule Module { get; set; } - public TypescriptType GetTypeScriptType(CodeTypeRef codeType) + public TypeScriptInterface() { - 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); - } + Members = new List(); } + } + + public class TypeScriptInterfaceAttributeValues + { + 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; } + } - private TypescriptType TryResolveType(CodeTypeRef codeType) - { - if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) - { - return new ArrayType() - { - ElementType = GetTypeScriptType(codeType.ElementType) - }; - } + public class TypeScriptModule + { + public string QualifiedName { get; set; } + public List Interfaces { get; set; } - return GetTypeScriptType(codeType.AsFullName); + /// + /// Returns true if this is the global namespace (ie. no module name) + /// + public bool IsGlobal + { + get { return string.IsNullOrWhiteSpace(QualifiedName); } } - private ArrayType TryResolveEnumerableType(string typeFullName) + public TypeScriptModule() { - return new ArrayType - { - ElementType = GetTypeScriptType(typeFullName) - }; + Interfaces = new List(); } + } + + public class ClassTraverser + { + public CodeClass CodeClass { get; private set; } + public Action WithProperty { get; set; } - public TypescriptType GetTypeScriptType(string typeFullName) + public ClassTraverser(CodeClass codeClass, Action withProperty) { - CustomType customType; - if (customTypes.TryGetValue(typeFullName, out customType)) - return customType; - - if (IsGenericEnumerable(typeFullName)) - { - return new ArrayType - { - ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) - }; - } - else if (IsNullable(typeFullName)) - { - return new NullableType - { - WrappedType = GetTypeScriptType(UnwrapGenericType(typeFullName)) - }; - } - - 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(); + if (codeClass == null) + throw new ArgumentNullException("codeClass"); + + if (withProperty == null) + throw new ArgumentNullException("withProperty"); - case "System.String": - case "System.DateTime": - return new StringType(); + this.CodeClass = codeClass; + this.WithProperty = withProperty; - default: - return new TypescriptType(); - } + if (codeClass.Members != null) + Traverse(codeClass.Members); } - private bool IsNullable(string typeFullName) + private void Traverse(CodeElements members) { - return typeFullName.StartsWith(nullableTypeStart); + foreach (var property in members.OfType()) + WithProperty(property); } + } + + public class NamespaceTraverser + { + public Action WithCodeClass { get; private set; } - public string UnwrapGenericType(string typeFullName) + public NamespaceTraverser(CodeNamespace ns, Action withCodeClass) { - int firstIndex = typeFullName.IndexOf('<'); - return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); + if (ns == null) + throw new ArgumentNullException("ns"); + + if (withCodeClass == null) + throw new ArgumentNullException("withCodeClass"); + + WithCodeClass = withCodeClass; + + if (ns.Members != null) + Traverse(ns.Members); } - public bool IsGenericEnumerable(string typeFullName) + private void Traverse(CodeElements members) { - return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); + foreach (var codeClass in members.OfType()) + WithCodeClass(codeClass); } } - public class InterfaceOutputAppender : OutputAppender + + public class ProjectTraverser { - private bool InGlobalModule { get; set; } - - public InterfaceOutputAppender(StringBuilder output, int baseIndentation, bool inGlobalModule) - : base(output, baseIndentation) - { - this.InGlobalModule = inGlobalModule; - } + public Action WithNamespace { get; private set; } - public override void AppendOutput(TypeScriptInterface tsInterface) + public ProjectTraverser(Project project, Action withNamespace) { - BeginInterface(tsInterface); - - AppendMembers(tsInterface); + if (project == null) + throw new ArgumentNullException("project"); - if (tsInterface.IndexedType != null) - AppendIndexer(tsInterface); + if (withNamespace == null) + throw new ArgumentNullException("withNamespace"); - EndInterface(); + WithNamespace = withNamespace; + + if (project.ProjectItems != null) + Traverse(project.ProjectItems); } - private void AppendMembers(TypeScriptInterface tsInterface) + private void Traverse(ProjectItems items) { - var appender = new MemberOutputAppender(Output, BaseIndentation + 4); - foreach (var member in tsInterface.Members) - appender.AppendOutput(member); + foreach (ProjectItem pi in items) + { + if (pi.FileCodeModel != null) + { + var codeElements = pi.FileCodeModel.CodeElements; + foreach (var ns in codeElements.OfType()) + WithNamespace(ns); + } + + if (pi.ProjectItems != null) + Traverse(pi.ProjectItems); + } } + } + + public class ArrayType: TypescriptType + { + public TypescriptType ElementType { get; set; } - private void BeginInterface(TypeScriptInterface tsInterface) + public override string ToString() { - AppendIndentedLine("/** Generated from " + tsInterface.FullName + " **/"); + return ElementType.ToString() + "[]"; + } + } + + public class BoolType: TypescriptType + { + public override string Name + { + get { return "bool"; } + } + } + + public class InterfaceType : TypescriptType + { + public TypeScriptInterfaceAttributeValues AttributeValues { get; private set; } - 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(" {"); + public string QualifedModule + { + get + { + if (AttributeValues == null) + return null; + + return AttributeValues.Module; + } } - private void EndInterface() + public override string Name { - AppendIndentedLine("}"); + get + { + if (!string.IsNullOrEmpty(AttributeValues.NamePrefix)) + return AttributeValues.NamePrefix + AttributeValues.Name; + + return AttributeValues.Name; + } } - private void AppendIndexer(TypeScriptInterface tsInterface) + public InterfaceType(TypeScriptInterfaceAttributeValues values) { - AppendIndendation(); - Output.AppendFormat(" [index: number]: {0};", tsInterface.IndexedType); - Output.AppendLine(); + AttributeValues = values; } - } - public class MemberOutputAppender : OutputAppender - { - public MemberOutputAppender(StringBuilder output, int baseIndentation) - : base(output, baseIndentation) + + public InterfaceType(string name) { + AttributeValues = new TypeScriptInterfaceAttributeValues + { + Name = name + }; } - public override void AppendOutput(TypeScriptInterfaceMember member) + public override string ToString() { - AppendIndendation(); - - bool isOptional = member.Optional || (member.Type is NullableType); + if (string.IsNullOrWhiteSpace(QualifedModule)) + return base.ToString(); - Output.AppendFormat("{0}{1}: {2}", - member.Name, - (isOptional ? "?" : ""), - member.Type - ); - - Output.AppendLine(";"); + return QualifedModule + "." + base.ToString(); } } - public class ModuleOutputAppender : OutputAppender + + public class NullableType : TypescriptType { - public ModuleOutputAppender(StringBuilder output, int baseIndentation) - : base(output, baseIndentation) + public TypescriptType WrappedType { get; set; } + + public override string ToString() { + return WrappedType.ToString(); } - - public override void AppendOutput(TypeScriptModule module) + } + + public class NumberType : TypescriptType + { + public override string Name { - BeginModule(module); - - var interfaceAppender = new InterfaceOutputAppender(Output, BaseIndentation + 4, module.IsGlobal); - foreach (var tsInterface in module.Interfaces) - interfaceAppender.AppendOutput(tsInterface); - - EndModule(module); + get { return "number"; } } - - private void BeginModule(TypeScriptModule module) + } + + public class StringType: TypescriptType + { + public override string Name { - if (module.IsGlobal) - { - Output.AppendLine("// -- Begin global interfaces"); - } - else - { - Output.Append("module "); - Output.Append(module.QualifiedName); - Output.AppendLine(" {"); - } + get { return "string"; } } + } - private void EndModule(TypeScriptModule module) + public class TypescriptType + { + public virtual string Name { get { return "any"; } } + + public override string ToString() { - if (module.IsGlobal) - Output.AppendLine("// -- End global interfaces"); - else - Output.AppendLine("}"); + return Name; } } - public abstract class OutputAppender where TSegment: class + + public class CodeTraverser { - protected StringBuilder Output { get; private set; } - protected int BaseIndentation { get; private set; } + public Project Project { get; private set; } + public Settings Settings { get; private set; } - public OutputAppender(StringBuilder output, int baseIndentation) + private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; + private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; + + public CodeTraverser(Project project, Settings settings) { - if (output == null) - throw new ArgumentNullException("output"); + if (project == null) + throw new ArgumentNullException("project"); - this.Output = output; - this.BaseIndentation = baseIndentation; - } + if (settings == null) + throw new ArgumentNullException("settings"); - public abstract void AppendOutput(TSegment segment); + this.Project = project; + this.Settings = settings; + } - protected void AppendIndented(string text) + public TypeContext BuildContext() { - AppendIndendation(); - Output.Append(text); + var typeContext = new TypeContext(); + var partialClasses = new Dictionary(); + + new ProjectTraverser(this.Project, (ns) => + { + new NamespaceTraverser(ns, (codeClass) => + { + CodeAttribute attribute; + if (!TryGetAttribute(codeClass.Attributes, InterfaceAttributeFullName, out attribute)) + return; + + var values = GetInterfaceValues(codeClass, attribute); + var interfaceType = new InterfaceType(values); + + if (!typeContext.ContainsInterfaceType(codeClass.FullName)) + typeContext.AddInterfaceType(codeClass.FullName, interfaceType); + }); + }); + + return typeContext; } - protected void AppendIndentedLine(string line) + public IEnumerable GetAllInterfaces() { - AppendIndendation(); - Output.AppendLine(line); + var typeContext = BuildContext(); + var byModuleName = new Dictionary(); + var tsMap = new Dictionary(); + + new ProjectTraverser(this.Project, (ns) => + { + new NamespaceTraverser(ns, (codeClass) => + { + InterfaceType interfaceType; + if (!typeContext.TryGetInterfaceType(codeClass.FullName, out interfaceType)) + return; + + var values = interfaceType.AttributeValues; + + 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); + }); + }); + + 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 string GetInterfaceName(TypeScriptInterfaceAttributeValues attributeValues) + { + if (!string.IsNullOrEmpty(attributeValues.NamePrefix)) + return attributeValues.NamePrefix + attributeValues.Name; - protected void AppendIndendation() + return attributeValues.Name; + } + + private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) { - Output.Append(' ', BaseIndentation); + var tsInterface = new TypeScriptInterface + { + FullName = codeClass.FullName, + Name = GetInterfaceName(attributeValues) + }; + + TypescriptType indexedType; + if (TryGetIndexedType(codeClass, typeContext, out indexedType)) + tsInterface.IndexedType = indexedType; + + new ClassTraverser(codeClass, (property) => + { + TypeScriptInterfaceMember member; + if (TryGetMember(property, typeContext, out member)) + tsInterface.Members.Add(member); + }); + + return tsInterface; } - public override string ToString() + private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) { - return Output.ToString(); + foreach (CodeAttribute attr in attributes) + { + if (attr.FullName == attributeFullName) + { + attribute = attr; + return true; + } + } + + attribute = null; + return false; } - } - public static class OutputFormatter - { - public static string GetOutput(List modules) + + private bool TryGetIndexedType(CodeClass codeClass, TypeContext typeContext, out TypescriptType indexedType) { - var output = new StringBuilder(); - - output.AppendLine("/****************************************************************************"); - output.AppendLine(" Generated by T4TS.tt - don't make any changes in this file"); - output.AppendLine("****************************************************************************/"); + indexedType = null; + if (codeClass.Bases == null || codeClass.Bases.Count == 0) + return false; - var moduleAppender = new ModuleOutputAppender(output, 0); - foreach (var module in modules) + foreach (CodeElement baseClass in codeClass.Bases) { - output.AppendLine(); - moduleAppender.AppendOutput(module); + if (typeContext.IsGenericEnumerable(baseClass.FullName)) + { + string fullName = typeContext.UnwrapGenericType(baseClass.FullName); + indexedType = typeContext.GetTypeScriptType(fullName); + return true; + } } - return output.ToString(); + return false; } - } - 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; } + private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + { + var values = GetAttributeValues(interfaceAttribute); - /// - /// The default value for the CamelCase flag for an interface member name, if not specified by the TypeScriptMemberAttribute - /// - public bool DefaultCamelCaseMemberNames { get; set; } + 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 + }; + } - /// - /// 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; } + private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + { + member = null; + if (property.Access != vsCMAccess.vsCMAccessPublic) + return false; - public List Members { get; set; } - public TypescriptType IndexedType { get; set; } - public TypeScriptInterface Parent { get; set; } - public TypeScriptModule Module { get; set; } + var getter = property.Getter; + if (getter == null) + return false; - public TypeScriptInterface() - { - Members = new List(); - } - } - public class TypeScriptInterfaceAttributeValues - { - 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; } + 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 InterfaceType(values.Type) + }; - /// - /// Returns true if this is the global namespace (ie. no module name) - /// - public bool IsGlobal - { - get { return string.IsNullOrWhiteSpace(QualifiedName); } - } + if (values.CamelCase && values.Name == null) + member.Name = member.Name.Substring(0, 1).ToLowerInvariant() + member.Name.Substring(1); - public TypeScriptModule() - { - Interfaces = new List(); + return true; } - } - public class ClassTraverser - { - public CodeClass CodeClass { get; private set; } - public Action WithProperty { get; set; } - public ClassTraverser(CodeClass codeClass, Action withProperty) + private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) { - if (codeClass == null) - throw new ArgumentNullException("codeClass"); - - if (withProperty == null) - throw new ArgumentNullException("withProperty"); + bool? attributeOptional = null; + bool? attributeCamelCase = null; + string attributeName = null; + string attributeType = null; - this.CodeClass = codeClass; - this.WithProperty = withProperty; + CodeAttribute attribute; + if (TryGetAttribute(property.Attributes, MemberAttributeFullName, out attribute)) + { + var values = GetAttributeValues(attribute); + if (values.ContainsKey("Optional")) + attributeOptional = values["Optional"] == "true"; - if (codeClass.Members != null) - Traverse(codeClass.Members); + if (values.ContainsKey("CamelCase")) + attributeCamelCase = values["CamelCase"] == "true"; + + values.TryGetValue("Name", out attributeName); + values.TryGetValue("Type", out attributeType); + } + + return new TypeScriptMemberAttributeValues + { + Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, + Name = attributeName, + Type = attributeType, + CamelCase = attributeCamelCase ?? Settings.DefaultCamelCaseMemberNames + }; } - private void Traverse(CodeElements members) + private Dictionary GetAttributeValues(CodeAttribute codeAttribute) { - foreach (var property in members.OfType()) - WithProperty(property); + 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); + + values.Add(property.Name, val); + } + + return values; } } - public class NamespaceTraverser + + public class TypeContext { - public Action WithCodeClass { get; private set; } + private static readonly string[] genericCollectionTypeStarts = new string[] { + "System.Collections.Generic.List<", + "System.Collections.Generic.IList<", + "System.Collections.Generic.ICollection<" + }; - public NamespaceTraverser(CodeNamespace ns, Action withCodeClass) + private static readonly string nullableTypeStart = "System.Nullable<"; + + /// + /// Lookup table for "interface types", ie. non-builtin types (typically classes or unknown types). Keyed on the FullName of the type. + /// + private Dictionary interfaceTypes = new Dictionary(); + + public void AddInterfaceType(string typeFullName, InterfaceType interfaceType) { - if (ns == null) - throw new ArgumentNullException("ns"); - - if (withCodeClass == null) - throw new ArgumentNullException("withCodeClass"); - - WithCodeClass = withCodeClass; - - if (ns.Members != null) - Traverse(ns.Members); + interfaceTypes.Add(typeFullName, interfaceType); } - private void Traverse(CodeElements members) + public bool TryGetInterfaceType(string typeFullName, out InterfaceType interfaceType) { - foreach (var codeClass in members.OfType()) - WithCodeClass(codeClass); + return interfaceTypes.TryGetValue(typeFullName, out interfaceType); } - } - public class ProjectTraverser - { - public Action WithNamespace { get; private set; } - public ProjectTraverser(Project project, Action withNamespace) + public bool ContainsInterfaceType(string typeFullName) { - if (project == null) - throw new ArgumentNullException("project"); - - if (withNamespace == null) - throw new ArgumentNullException("withNamespace"); - - WithNamespace = withNamespace; - - if (project.ProjectItems != null) - Traverse(project.ProjectItems); + return interfaceTypes.ContainsKey(typeFullName); } - private void Traverse(ProjectItems items) + public TypescriptType GetTypeScriptType(CodeTypeRef codeType) { - foreach (ProjectItem pi in items) + switch (codeType.TypeKind) { - if (pi.FileCodeModel != null) - { - var codeElements = pi.FileCodeModel.CodeElements; - foreach (var ns in codeElements.OfType()) - WithNamespace(ns); - } + case vsCMTypeRef.vsCMTypeRefChar: + case vsCMTypeRef.vsCMTypeRefString: + return new StringType(); - if (pi.ProjectItems != null) - Traverse(pi.ProjectItems); + 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); } } - } - public class ArrayType: TypescriptType - { - public TypescriptType ElementType { get; set; } - public override string ToString() - { - return ElementType.ToString() + "[]"; - } - } - public class BoolType: TypescriptType - { - public override string Name + private TypescriptType TryResolveType(CodeTypeRef codeType) { - get { return "bool"; } - } - } - public class CustomType: TypescriptType - { - private string m_name; + if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) + { + return new ArrayType() + { + ElementType = GetTypeScriptType(codeType.ElementType) + }; + } - public override string Name - { - get { return m_name; } + return GetTypeScriptType(codeType.AsFullName); } - - public string QualifedModule { get; private set; } - public CustomType(string name, string qualifiedModule=null) + private ArrayType TryResolveEnumerableType(string typeFullName) { - m_name = name; - this.QualifedModule = qualifiedModule; + return new ArrayType + { + ElementType = GetTypeScriptType(typeFullName) + }; } - public override string ToString() + public TypescriptType GetTypeScriptType(string typeFullName) { - if (string.IsNullOrWhiteSpace(QualifedModule)) - return base.ToString(); + InterfaceType interfaceType; + if (interfaceTypes.TryGetValue(typeFullName, out interfaceType)) + return interfaceType; - return QualifedModule + "." + base.ToString(); - } - } - public class NullableType : TypescriptType - { - public TypescriptType WrappedType { get; set; } + if (IsGenericEnumerable(typeFullName)) + { + return new ArrayType + { + ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } + else if (IsNullable(typeFullName)) + { + return new NullableType + { + WrappedType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } - public override string ToString() - { - return WrappedType.ToString(); + 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 class NumberType : TypescriptType - { - public override string Name + + private bool IsNullable(string typeFullName) { - get { return "number"; } + return typeFullName.StartsWith(nullableTypeStart); } - } - public class StringType: TypescriptType - { - public override string Name + + public string UnwrapGenericType(string typeFullName) { - get { return "string"; } + int firstIndex = typeFullName.IndexOf('<'); + return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); } - } - public class TypescriptType - { - public virtual string Name { get { return "any"; } } - public override string ToString() + public bool IsGenericEnumerable(string typeFullName) { - return Name; + return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); } } #> \ No newline at end of file