From 3d3af4fff88eee0646404b2143739a1f3ffe5486 Mon Sep 17 00:00:00 2001 From: Christoffer Skeppstedt Date: Fri, 16 Nov 2012 01:06:11 +0100 Subject: [PATCH] Adding the 1.0 build version --- build/T4TS.cs | 51 ++- build/T4TS.tt | 735 +++++++++++++++++++++++++++----------- build/T4TS.tt.settings.t4 | 18 + 3 files changed, 593 insertions(+), 211 deletions(-) create mode 100644 build/T4TS.tt.settings.t4 diff --git a/build/T4TS.cs b/build/T4TS.cs index 0a219a1..85cf00e 100644 --- a/build/T4TS.cs +++ b/build/T4TS.cs @@ -1,14 +1,53 @@ -namespace T4TS + +namespace T4TS { using System; using System.CodeDom; using System.CodeDom.Compiler; - [GeneratedCode("T4TS", "0.1")] - public class TypeScriptInterfaceAttribute : Attribute + /// + /// Add this attribute to a class to generate a corresponding TypeScript interface. + /// + [GeneratedCode("T4TS", "1.0")] + [AttributeUsage(AttributeTargets.Class, AllowMultiple=false, Inherited=true)] + public class TypeScriptInterfaceAttribute: Attribute { - public TypeScriptInterfaceAttribute() - { - } + /// + /// Specifies which module the interface should be placed. + /// The default module will be used if not specified. + /// + public string Module { get; set; } + + /// + /// The name of the interface. + /// If not specified, the name of the class will be used. + /// + public string Name { get; set; } + } + + /// + /// Add this attribute to a property to customize the generated interface member + /// + [GeneratedCode("T4TS", "1.0")] + [AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)] + public class TypeScriptMemberAttribute: Attribute + { + /// + /// The member name in the interface. + /// If not specified, the property name will be used. + /// + public string Name { get; set; } + + /// + /// Specify if the member should be optional, ie. "name?: type". + /// If not specified, the default value will be used. + /// + public bool Optional { get; set; } + + /// + /// Specify which type the interface member will have. + /// If not specified, a suitable type will be determined. + /// + public string Type { get; set; } } } diff --git a/build/T4TS.tt b/build/T4TS.tt index 143817b..e32a61a 100644 --- a/build/T4TS.tt +++ b/build/T4TS.tt @@ -1,90 +1,122 @@ <#@ template language="C#" debug="true" hostspecific="true" #><#@ output extension=".cs" #><#@ assembly name="System.Core" -#><#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" -#><#@ assembly name="EnvDTE" -#><#@ assembly name="EnvDTE80" -#><#@ assembly name="VSLangProj" -#><#@ assembly name="System.Xml" -#><#@ assembly name="System.Xml.Linq" +#><#@ 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="System.Text.RegularExpressions" #><#@ import namespace="Microsoft.VisualStudio.Shell.Interop" -#><#@ import namespace="EnvDTE" -#><#@ import namespace="EnvDTE80" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" -#><# PrepareDataToRender(this); var manager = Manager.Create(Host, GenerationEnvironment); manager.StartNewFile("T4TS.d.ts"); #> -/************************************************ - Generated by T4TS - Changes made in this file will be overwritten. -************************************************/ - -module Api { - -<# foreach(var tsInterface in output) { #> +#><#@ import namespace="EnvDTE" +#><#@ Include File="T4TS.tt.settings.t4" +#><# var manager = Manager.Create(Host, GenerationEnvironment); manager.StartNewFile("T4TS.d.ts"); #> +/**************************************************************************** + 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) { #> /** Generated from <#= tsInterface.FullName #> **/ - export interface <#= tsInterface.Name #> { + <#= module.IsGlobal?"":"export "#>interface <#= tsInterface.Name #> { <# foreach(var member in tsInterface.Members) { #> - <#= member.Name #>: <#=member.Type #>; + <#= member.Name + (member.Optional ? "?" : "") #>: <#=member.Type #>; <# } #> -<# if (tsInterface.IndexerType != null) { #> - [index: number]: <#= tsInterface.IndexerType #>; +<# if (tsInterface.IndexedType != null) { #> + [index: number]: <#= tsInterface.IndexedType #>; <# } #> } - <# } #> -} -<#manager.EndBlock(); -#> +<#= module.IsGlobal ? "// -- End global interfaces" : "}" #> +<# } #> +<# manager.EndBlock(); #> + namespace T4TS { using System; using System.CodeDom; using System.CodeDom.Compiler; - [GeneratedCode("T4TS", "0.1")] - public class TypeScriptInterfaceAttribute : Attribute + /// + /// Add this attribute to a class to generate a corresponding TypeScript interface. + /// + [GeneratedCode("T4TS", "1.0")] + [AttributeUsage(AttributeTargets.Class, AllowMultiple=false, Inherited=true)] + public class TypeScriptInterfaceAttribute: Attribute { - public TypeScriptInterfaceAttribute() - { - } + /// + /// Specifies which module the interface should be placed. + /// The default module will be used if not specified. + /// + public string Module { get; set; } + + /// + /// The name of the interface. + /// If not specified, the name of the class will be used. + /// + public string Name { get; set; } + } + + /// + /// Add this attribute to a property to customize the generated interface member + /// + [GeneratedCode("T4TS", "1.0")] + [AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)] + public class TypeScriptMemberAttribute: Attribute + { + /// + /// The member name in the interface. + /// If not specified, the property name will be used. + /// + public string Name { get; set; } + + /// + /// Specify if the member should be optional, ie. "name?: type". + /// If not specified, the default value will be used. + /// + public bool Optional { get; set; } + + /// + /// Specify which type the interface member will have. + /// If not specified, a suitable type will be determined. + /// + public string Type { get; set; } } } -<#manager.Process(true); #><#+ - -static DTE Dte; -static Project Project; -static TextTransformation TT; -static string attributeFullName = "T4TS.TypeScriptInterfaceAttribute"; -static List output; +<# +manager.Process(true); +#><#+ -void PrepareDataToRender(TextTransformation tt) { - TT = tt; +List GetDataToRender() { + DTE dte = null; // Get the DTE service from the host var serviceProvider = Host as IServiceProvider; - if (serviceProvider != null) { - Dte = serviceProvider.GetService(typeof(SDTE)) as DTE; - } + if (serviceProvider != null) + dte = serviceProvider.GetService(typeof(SDTE)) as DTE; // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe - if (Dte == null) { + if (dte == null) throw new Exception("Can only execute through the Visual Studio host"); - } - Project = GetProjectContainingT4File(Dte); - - if (Project == null) { - Error("Could not find the VS Project containing the T4TS file."); - return; - } + var project = GetProjectContainingT4File(dte); + + if (project == null) + throw new Exception("Could not find the VS project containing the T4TS file."); + + // Read settings from T4TS.tt.settings.tt + var settings = new Settings + { + DefaultModule = DefaultModule, + DefaultOptional = DefaultOptional + }; - var generator = new CodeGenerator(Project); + var generator = new CodeGenerator(project, settings); - output = generator.GetInterfaces().ToList(); + return generator.GetAllInterfaces().ToList(); } Project GetProjectContainingT4File(DTE dte) { @@ -104,148 +136,252 @@ Project GetProjectContainingT4File(DTE dte) { return projectItem.ContainingProject; } -public class CodeGenerator +// -- Models ---------------------------------------------------------------------------------- + +class Settings { - public Project Project { get; private set; } - private static readonly string AttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; + /// + /// 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; } +} +class TypeScriptInterface +{ + public string Name { get; set; } + public string FullName { get; set; } - private static readonly string[] genericCollectionTypeStarts = new string[] { - "System.Collections.Generic.List<", - "System.Collections.Generic.IList<", - "System.Collections.Generic.ICollection<" - }; + public List Members { get; set; } + public TypescriptType IndexedType { get; set; } - public CodeGenerator(Project project) + 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; + public TypeScriptModule() + { + Interfaces = new List(); + } +} + +// -- Traversal ---------------------------------------------------------------------------------- + +class ClassTraverser +{ + public CodeClass CodeClass { get; private set; } + public Action WithProperty { get; set; } + + public ClassTraverser(CodeClass codeClass, Action withProperty) + { + 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); } +} +class NamespaceTraverser +{ + public Action WithCodeClass { get; private set; } + + 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); + } +} +class ProjectTraverser +{ + public Action WithNamespace { get; private set; } - public IEnumerable GetInterfaces() + public ProjectTraverser(Project project, Action withNamespace) { - // A context that holds all interfaces that will be generated. - // Keyed on the FullName of the CodeType. - var typeContext = new TypeContext(VisitProjectItems(Project.ProjectItems) - .ToDictionary(i => i.CodeType.FullName, i => i)); + if (project == null) + throw new ArgumentNullException("project"); + + if (withNamespace == null) + throw new ArgumentNullException("withNamespace"); - foreach (var instance in typeContext.Values) - yield return GetInterface(instance, typeContext); + WithNamespace = withNamespace; + + if (project.ProjectItems != null) + Traverse(project.ProjectItems); } - private IEnumerable VisitProjectItems(ProjectItems items) + private void Traverse(ProjectItems items) { foreach (ProjectItem pi in items) { if (pi.FileCodeModel != null) { var codeElements = pi.FileCodeModel.CodeElements; - foreach (CodeElement codeElement in codeElements) - { - if (codeElement is CodeNamespace) - { - foreach (var instance in FindClassesWithAttribute(codeElement as CodeNamespace)) - yield return instance; - } - } + foreach (var ns in codeElements.OfType()) + WithNamespace(ns); } - if (pi.ProjectItems != null && pi.ProjectItems.Count > 0) - { - foreach (var tsInterface in VisitProjectItems(pi.ProjectItems)) - yield return tsInterface; - } + if (pi.ProjectItems != null) + Traverse(pi.ProjectItems); } } +} - private IEnumerable FindClassesWithAttribute(CodeNamespace ns) - { - foreach (CodeElement codeElement in ns.Members) - { - if (codeElement is CodeType) - { - var ct = codeElement as CodeType; - if (ct.Attributes == null) - continue; +// -- Types ---------------------------------------------------------------------------------- - foreach (CodeAttribute attr in ct.Attributes) - { - if (attr.FullName == AttributeFullName) - { - yield return new AttributeDecoratedInstance - { - CodeType = ct, - Namespace = ns, - TypescriptType = ct.Name - }; - - break; - } - } - } - } +class TypescriptType +{ + public virtual string Name { get { return "any"; } } + + 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; } - private TypeScriptInterface GetInterface(AttributeDecoratedInstance instance, TypeContext typeContext) + public override string ToString() { - var tsInterface = new TypeScriptInterface - { - FullName = instance.CodeType.FullName, - Name = instance.CodeType.Name, - Members = GetMembers(instance, typeContext).ToList() - }; + return ElementType.ToString() + "[]"; + } +} +class CustomType: TypescriptType +{ + private string m_name; - if (instance.CodeType.Bases.Count > 0) - { - foreach (CodeElement elem in instance.CodeType.Bases) - { - if (genericCollectionTypeStarts.Any(elem.FullName.StartsWith)) - { - string fullName = UnwrapGenericType(elem.FullName); - if (typeContext.ContainsKey(fullName)) - { - tsInterface.IndexerType = typeContext[fullName].CodeType.Name; - return tsInterface; - } - } - } - } + public override string Name + { + get { return m_name; } + } + + public string QualifedModule { get; private set; } - return tsInterface; + public CustomType(string name, string qualifiedModule=null) + { + m_name = name; + this.QualifedModule = qualifiedModule; } - private IEnumerable GetMembers(AttributeDecoratedInstance instance, TypeContext typeContext) + public override string ToString() { - foreach (CodeElement codeElement in instance.CodeType.Members) - { - if (!(codeElement is CodeProperty)) - continue; + if (string.IsNullOrWhiteSpace(QualifedModule)) + return base.ToString(); - var codeProperty = (CodeProperty)codeElement; - if (codeProperty.Access != vsCMAccess.vsCMAccessPublic) - continue; + 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 func = codeProperty.Getter; - if (func != null) - { - yield return new TypeScriptInterfaceMember - { - Name = codeProperty.Name, - Type = GetTypeScriptType(instance, func.Type, codeProperty, typeContext) - }; - } - } + /// + /// 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); } - private string GetTypeScriptType(AttributeDecoratedInstance instance, CodeTypeRef codeType, CodeProperty codeProperty, TypeContext typeContext) + public TypescriptType GetTypeScriptType(CodeTypeRef codeType) { switch (codeType.TypeKind) { case vsCMTypeRef.vsCMTypeRefChar: case vsCMTypeRef.vsCMTypeRefString: - return "string"; + return new StringType(); case vsCMTypeRef.vsCMTypeRefBool: - return "bool"; + return new BoolType(); case vsCMTypeRef.vsCMTypeRefByte: case vsCMTypeRef.vsCMTypeRefDouble: @@ -254,40 +390,47 @@ public class CodeGenerator case vsCMTypeRef.vsCMTypeRefFloat: case vsCMTypeRef.vsCMTypeRefLong: case vsCMTypeRef.vsCMTypeRefDecimal: - return "number"; + return new NumberType(); default: - return TryResolveType(instance, codeType, codeProperty, typeContext); + return TryResolveType(codeType); } } - private string TryResolveType(AttributeDecoratedInstance instance, CodeTypeRef codeType, CodeProperty codeProperty, TypeContext typeContext) + private TypescriptType TryResolveType(CodeTypeRef codeType) { if (codeType.TypeKind == vsCMTypeRef.vsCMTypeRefArray) { - string typeFullName = codeType.ElementType.AsFullName; - return TryResolveEnumerableType(typeFullName, typeContext); - } - - if (genericCollectionTypeStarts.Any(s => codeType.AsFullName.StartsWith(s))) - { - string fullName = UnwrapGenericType(codeType); - return TryResolveEnumerableType(fullName, typeContext); + return new ArrayType() + { + ElementType = GetTypeScriptType(codeType.ElementType) + }; } - return TryResolveUnknownType(codeType.AsFullName, typeContext); + return GetTypeScriptType(codeType.AsFullName); } - private string TryResolveEnumerableType(string typeFullName, TypeContext typeContext) + private ArrayType TryResolveEnumerableType(string typeFullName) { - return TryResolveUnknownType(typeFullName, typeContext) + "[]"; + return new ArrayType + { + ElementType = GetTypeScriptType(typeFullName) + }; } - private string TryResolveUnknownType(string typeFullName, TypeContext typeContext) + public TypescriptType GetTypeScriptType(string typeFullName) { - AttributeDecoratedInstance instance; - if (typeContext.TryGetValue(typeFullName, out instance)) - return instance.CodeType.Name; + CustomType customType; + if (customTypes.TryGetValue(typeFullName, out customType)) + return customType; + + if (IsGenericEnumerable(typeFullName)) + { + return new ArrayType + { + ElementType = GetTypeScriptType(UnwrapGenericType(typeFullName)) + }; + } switch (typeFullName) { @@ -302,65 +445,247 @@ public class CodeGenerator case "System.Byte": case "System.SByte": case "System.Single": - return "number"; + return new NumberType(); case "System.String": case "System.DateTime": - return "string"; - - case "System.Object": - return "Object"; + return new StringType(); default: - return "any"; + return new TypescriptType(); } } - private string UnwrapGenericType(CodeTypeRef codeType) + public string UnwrapGenericType(string typeFullName) { - return UnwrapGenericType(codeType.AsFullName); + int firstIndex = typeFullName.IndexOf('<'); + return typeFullName.Substring(firstIndex+1, typeFullName.Length - firstIndex- 2); } - private string UnwrapGenericType(string typeFullName) + public bool IsGenericEnumerable(string typeFullName) { - return typeFullName.Split('<', '>')[1]; + return genericCollectionTypeStarts.Any(t => typeFullName.StartsWith(t)); } } -public class TypeContext: Dictionary +// -- Code generation -------------------------------------------------------------------------- + +class CodeGenerator { - public TypeContext(IDictionary dictionary): base(dictionary) + public Project Project { get; private set; } + public Settings Settings { get; private set; } + + private static readonly string InterfaceAttributeFullName = "T4TS.TypeScriptInterfaceAttribute"; + private static readonly string MemberAttributeFullName = "T4TS.TypeScriptMemberAttribute"; + + public CodeGenerator(Project project, Settings settings) { + if (project == null) + throw new ArgumentNullException("project"); + + if (settings == null) + throw new ArgumentNullException("settings"); + this.Project = project; + this.Settings = settings; } -} - -public class TypeScriptInterface -{ - public string Name { get; set; } - public string FullName { get; set; } - public List Members { get; set; } - public string IndexerType { get; set; } -} - -public class AttributeDecoratedInstance -{ - public CodeNamespace Namespace { get; set; } - public CodeType CodeType { get; set; } - public string TypescriptType { get; set; } -} -public class TypeScriptInterfaceMember -{ - public string Name { get; set; } - public string Type { get; set; } + public TypeContext BuildContext() + { + var typeContext = new TypeContext(); + + 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 customType = new CustomType(values.Name, values.Module); + + typeContext.AddCustomType(codeClass.FullName, customType); + }); + }); + + return typeContext; + } + + public IEnumerable GetAllInterfaces() + { + var typeContext = BuildContext(); + var byModuleName = 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; + + 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); + } + + module.Interfaces.Add(BuildInterface(codeClass, values, typeContext)); + }); + }); + + return byModuleName.Values + .OrderBy(m => m.QualifiedName) + .ToList(); + } + + private TypeScriptInterface BuildInterface(CodeClass codeClass, TypeScriptInterfaceAttributeValues attributeValues, TypeContext typeContext) + { + var tsInterface = new TypeScriptInterface + { + FullName = codeClass.FullName, + Name = attributeValues.Name + }; + + 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; + } + + private bool TryGetAttribute(CodeElements attributes, string attributeFullName, out CodeAttribute attribute) + { + foreach (CodeAttribute attr in attributes) + { + if (attr.FullName == attributeFullName) + { + attribute = attr; + return true; + } + } + + 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) + { + if (typeContext.IsGenericEnumerable(baseClass.FullName)) + { + string fullName = typeContext.UnwrapGenericType(baseClass.FullName); + indexedType = typeContext.GetTypeScriptType(fullName); + return true; + } + } + + return false; + } + + private TypeScriptInterfaceAttributeValues GetInterfaceValues(CodeClass codeClass, CodeAttribute interfaceAttribute) + { + var values = GetAttributeValues(interfaceAttribute); + + return new TypeScriptInterfaceAttributeValues + { + Name = values.ContainsKey("Name") ? values["Name"] : codeClass.Name, + Module = values.ContainsKey("Module") ? values["Module"] : Settings.DefaultModule ?? "T4TS", + }; + } + + private bool TryGetMember(CodeProperty property, TypeContext typeContext, out TypeScriptInterfaceMember member) + { + member = null; + if (property.Access != vsCMAccess.vsCMAccessPublic) + return false; + + var getter = property.Getter; + if (getter == null) + return false; + + 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) + }; + + return true; + } + + private TypeScriptMemberAttributeValues GetMemberValues(CodeProperty property, TypeContext typeContext) + { + bool? attributeOptional = 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"; + + values.TryGetValue("Name", out attributeName); + values.TryGetValue("Type", out attributeType); + } + + return new TypeScriptMemberAttributeValues + { + Optional = attributeOptional.HasValue ? attributeOptional.Value : Settings.DefaultOptional, + Name = attributeName, + Type = attributeType + }; + } + + 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); + + values.Add(property.Name, val); + } + + return values; + } } +// ------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------- // https://raw.github.com/damieng/DamienGKit // http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited -// ------------------------------------------------------------------------------- // Manager class records the various blocks so it can split them up class Manager { diff --git a/build/T4TS.tt.settings.t4 b/build/T4TS.tt.settings.t4 new file mode 100644 index 0000000..43965d5 --- /dev/null +++ b/build/T4TS.tt.settings.t4 @@ -0,0 +1,18 @@ +<#+ +/************************************************ + Settings for T4TS.tt +************************************************/ + +// The default module of the generated interface. If a module is +// not specified by the TypeScriptInterfaceAttribute, the interface +// will belong to this module (may be empty, in which case the +// interface will be globally accessible). +const string DefaultModule = "T4TS"; + +// The default value for the Optional flag for an interface member. +// If not specified by the TypeScriptInterfaceAttribute, the Optional +// flag will be set to this value. If the Optional flag is true, the +// generated member will look like "member?: type" instead of "member: type". +const bool DefaultOptional = false; + +#> \ No newline at end of file