/// <summary> /// Generate a javascript "class" /// </summary> private static void GenerateTypeDocumentation(TextWriter writer, Type type, XmlDocument xmlDoc, ConsoleParameters parms, List <Type> enumerationTypes, List <Type> alreadyGenerated, Dictionary <String, object> metaData) { if (parms.NoAbstract && type.IsAbstract) { return; } var propertyData = new Dictionary <String, object>(); if (!type.IsAbstract && !type.IsInterface && !type.IsGenericTypeDefinition) { metaData.Add(type.Name, propertyData); } if (alreadyGenerated.Contains(type)) { return; } else { alreadyGenerated.Add(type); } writer.WriteLine("// {0}", type.AssemblyQualifiedName); writer.WriteLine("//if(!{0})", type.GetCustomAttribute <JsonObjectAttribute>().Id); writer.WriteLine("/**"); writer.WriteLine(" * @class"); writer.WriteLine(" * @constructor"); writer.WriteLine(" * @public"); if (type.IsAbstract) { writer.WriteLine(" * @abstract"); } var jobject = type.GetCustomAttribute <JsonObjectAttribute>(); if (type.BaseType != typeof(Object) && (!type.BaseType.IsAbstract ^ !parms.NoAbstract)) { writer.WriteLine(" * @extends {0}", type.BaseType.GetCustomAttribute <JsonObjectAttribute>().Id); } // Lookup the summary information var typeDoc = xmlDoc.SelectSingleNode(String.Format("//*[local-name() = 'member'][@name = 'T:{0}']", type.FullName)); if (typeDoc != null) { if (typeDoc.SelectSingleNode(".//*[local-name() = 'summary']") != null) { writer.WriteLine(" * @summary {0}", TransformXDoc(typeDoc.SelectSingleNode(".//*[local-name() = 'summary']"))); } if (typeDoc.SelectSingleNode(".//*[local-name() = 'remarks']") != null) { writer.WriteLine(" * @description {0}", TransformXDoc(typeDoc.SelectSingleNode(".//*[local-name() = 'remarks']"))); } if (typeDoc.SelectSingleNode(".//*[local-name() = 'example']") != null) { writer.WriteLine(" * @example {0}", typeDoc.SelectSingleNode(".//*[local-name() = 'example']").InnerText.Replace("\r\n", "")); } } List <KeyValuePair <String, String> > copyCommands = new List <KeyValuePair <string, string> >(); Dictionary <String, String> propDocs = new Dictionary <string, string>(); // Get all properties and document them foreach (var itm in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (itm.GetCustomAttribute <JsonPropertyAttribute>() == null && itm.GetCustomAttribute <SerializationReferenceAttribute>() == null) { continue; } Type itmType = itm.PropertyType; if (itmType.IsGenericType) { itmType = itmType.GetGenericArguments()[0]; } var itmJobject = itmType.GetCustomAttribute <JsonObjectAttribute>(); if (itmJobject == null) { if (itmType.StripNullable().IsEnum) { itmJobject = new JsonObjectAttribute(String.Format("{0}", itmType.Name)); } else if (!primitives.TryGetValue(itmType, out itmJobject)) { itmJobject = new JsonObjectAttribute(itmType.Name); } } else { itmJobject = new JsonObjectAttribute(String.Format("{0}", itmJobject.Id)); } var simpleAtt = itmType.GetCustomAttribute <SimpleValueAttribute>(); if (simpleAtt != null) { var simpleProperty = itmType.GetProperty(simpleAtt.ValueProperty); if (!primitives.TryGetValue(simpleProperty.PropertyType, out itmJobject)) { itmJobject = new JsonObjectAttribute(simpleProperty.PropertyType.Name); } } var originalType = itmJobject.Id; // Is this a classified object? if so then the classifier values act as properties themselves var classAttr = itmType.GetCustomAttribute <ClassifierAttribute>(); if (classAttr != null && itm.PropertyType.IsGenericType) { itmJobject = new JsonObjectAttribute("object"); } else if (itm.Name.Contains("TimeXml") || itm.Name.Contains("DateXml")) // XML Representations of offsets { itmJobject = new JsonObjectAttribute("Date"); } writer.Write(" * @property {{{0}}} ", itmJobject.Id); var jprop = itm.GetCustomAttribute <JsonPropertyAttribute>(); var redir = itm.GetCustomAttribute <SerializationReferenceAttribute>(); if (jprop != null) { writer.Write(jprop.PropertyName); copyCommands.Add(new KeyValuePair <String, String>(jprop.PropertyName, itmJobject.Id)); } else if (redir != null) { var backingProperty = type.GetProperty(redir.RedirectProperty); jprop = backingProperty.GetCustomAttribute <JsonPropertyAttribute>(); writer.Write("{0}Model [Delay loaded from {0}], ", jprop.PropertyName); copyCommands.Add(new KeyValuePair <String, String>(jprop.PropertyName + "Model", itmJobject.Id)); } else { writer.Write(itm.Name + "Model"); copyCommands.Add(new KeyValuePair <string, string>(itm.Name + "Model", itmJobject.Id)); } // We're going to add this to metadata var propertyInfo = new Dictionary <String, Object>(); if (propertyData.TryGetValue(jprop.PropertyName, out object cValue)) { propertyInfo = (Dictionary <String, Object>)cValue; if (propertyInfo["type"].Equals("Guid")) { propertyInfo["type"] = itm.PropertyType.StripGeneric().Name; } } else { propertyData.Add(jprop.PropertyName, propertyInfo); // Now - let's add some info propertyInfo.Add("isCollection", typeof(ICollection).IsAssignableFrom(itm.PropertyType)); propertyInfo.Add("type", itm.PropertyType.StripGeneric().Name); } if (propertyInfo["isCollection"].Equals(true)) // Classifier? { var classifier = itm.PropertyType.StripGeneric().GetCustomAttribute <ClassifierAttribute>(); if (classifier != null) { var classifierProperty = itm.PropertyType.StripGeneric().GetProperty(classifier.ClassifierProperty); propertyInfo.Add("classifierType", classifierProperty.PropertyType.StripGeneric().Name); var sredir = classifierProperty.GetCustomAttribute <SerializationReferenceAttribute>(); if (sredir != null) { classifierProperty = itm.PropertyType.StripGeneric().GetProperty(sredir.RedirectProperty); } var binding = classifierProperty.GetCustomAttribute <BindingAttribute>(); if (binding != null) { propertyInfo.Add("classifierValues", binding.Binding.GetFields().Where(r => r.FieldType == typeof(Guid)).Select(o => o.Name)); } } } else { var binding = itm.GetCustomAttribute <BindingAttribute>(); if (binding != null) { propertyInfo.Add("values", binding.Binding.GetFields().Where(r => r.FieldType == typeof(Guid)).Select(o => o.Name)); } } // Output documentation typeDoc = xmlDoc.SelectSingleNode(String.Format("//*[local-name() = 'member'][@name = 'P:{0}.{1}']", itm.DeclaringType.FullName, itm.Name)); if (typeDoc != null) { var docNode = typeDoc.SelectSingleNode(".//*[local-name() = 'summary']"); if (docNode != null) { var jsDoc = TransformXDoc(docNode); if (propDocs.TryGetValue(jprop.PropertyName, out string edoc)) { if (edoc.Length < jsDoc.Length) { propDocs[jprop.PropertyName] = jsDoc; } } else { propDocs.Add(jprop.PropertyName, jsDoc); } writer.Write($" {jsDoc}"); } } var bindAttr = itm.GetCustomAttribute <BindingAttribute>(); if (itmType.StripNullable().IsEnum) { bindAttr = new BindingAttribute(itmType.StripNullable()); } if (bindAttr != null) { enumerationTypes.Add(bindAttr.Binding); writer.Write("(see: {{@link {0}}} for values)", bindAttr.Binding.Name); } writer.WriteLine(); // Classified object? If so we need to clarify how the object is propogated if (classAttr != null && itm.PropertyType.IsGenericType) { // Does the classifier have a binding var classProperty = itmType.GetProperty(classAttr.ClassifierProperty); if (classProperty.GetCustomAttribute <SerializationReferenceAttribute>() != null) { classProperty = itmType.GetProperty(classProperty.GetCustomAttribute <SerializationReferenceAttribute>().RedirectProperty); } bindAttr = classProperty.GetCustomAttribute <BindingAttribute>(); if (bindAttr != null) { enumerationTypes.Add(bindAttr.Binding); // Binding attribute found so lets enumerate it foreach (var fi in bindAttr.Binding.GetFields(BindingFlags.Public | BindingFlags.Static)) { writer.Write(" * @property {{{0}}} {1}.{2} ", originalType, jprop.PropertyName, fi.Name, classProperty.GetCustomAttribute <JsonPropertyAttribute>()?.PropertyName); typeDoc = xmlDoc.SelectSingleNode(String.Format("//*[local-name() = 'member'][@name = 'F:{0}.{1}']", fi.DeclaringType.FullName, fi.Name)); if (typeDoc != null) { if (typeDoc.SelectSingleNode(".//*[local-name() = 'summary']") != null) { writer.Write(typeDoc.SelectSingleNode(".//*[local-name() = 'summary']").InnerText.Replace("\r\n", "")); } } writer.WriteLine(); } writer.WriteLine(" * @property {{{0}}} {1}.$other Unclassified", originalType, jprop.PropertyName); } else { writer.Write(" * @property {{{0}}} {1}.{2} ", originalType, jprop.PropertyName, "classifier"); writer.Write(" where classifier is from {{@link {0}}} {1}", classProperty.DeclaringType.GetCustomAttribute <JsonObjectAttribute>().Id, classProperty.GetCustomAttribute <JsonPropertyAttribute>()?.PropertyName); writer.WriteLine(); } } } writer.WriteLine(" * @param {{{0}}} copyData Copy constructor (if present)", jobject.Id); writer.WriteLine(" */"); writer.WriteLine("function {0} (copyData) {{ ", jobject.Id); writer.WriteLine("\tthis.$type = '{0}';", jobject.Id); writer.WriteLine("\tif(copyData) {"); copyCommands.Reverse(); // Get all properties and document them foreach (var itm in copyCommands.Where(o => o.Key != "$type")) { writer.WriteLine("\t/**"); if (propDocs.TryGetValue(itm.Key, out string doc)) { writer.WriteLine("\t * @summary {0}", doc); } writer.WriteLine("\t * @type {{{0}}} ", itm.Value); writer.WriteLine("\t */"); writer.WriteLine("\tthis.{0} = copyData.{0};", itm.Key); } writer.WriteLine("\t}"); writer.WriteLine("}} // {0} ", jobject.Id); }
/// <summary> /// Generate enumeration documentation /// </summary> private static void GenerateEnumerationDocumentation(TextWriter writer, Type type, XmlDocument xmlDoc, ConsoleParameters parms) { var jobject = type.GetCustomAttribute <JsonObjectAttribute>(); if (jobject == null) { jobject = new JsonObjectAttribute(type.Name); } writer.WriteLine("// {0}", type.AssemblyQualifiedName); writer.WriteLine("// if(!{0})", jobject.Id); writer.WriteLine("/**"); writer.WriteLine(" * @enum {string}"); writer.WriteLine(" * @public"); writer.WriteLine(" * @readonly"); // Lookup the summary information var typeDoc = xmlDoc.SelectSingleNode(String.Format("//*[local-name() = 'member'][@name = 'T:{0}']", type.FullName)); if (typeDoc != null) { if (typeDoc.SelectSingleNode(".//*[local-name() = 'summary']") != null) { writer.WriteLine(" * @summary {0}", typeDoc.SelectSingleNode(".//*[local-name() = 'summary']").InnerText.Replace("\r\n", "")); } if (typeDoc.SelectSingleNode(".//*[local-name() = 'remarks']") != null) { writer.WriteLine(" * @description {0}", typeDoc.SelectSingleNode(".//*[local-name() = 'remarks']").InnerText.Replace("\r\n", "")); } if (typeDoc.SelectSingleNode(".//*[local-name() = 'example']") != null) { writer.WriteLine(" * @example {0}", typeDoc.SelectSingleNode(".//*[local-name() = 'example']").InnerText.Replace("\r\n", "")); } } writer.WriteLine(" */"); writer.WriteLine("const {0} = {{ ", jobject.Id); // Enumerate fields foreach (var fi in type.GetFields(BindingFlags.Public | BindingFlags.Static)) { writer.WriteLine("\t/** "); writer.Write("\t * "); typeDoc = xmlDoc.SelectSingleNode(String.Format("//*[local-name() = 'member'][@name = 'F:{0}.{1}']", fi.DeclaringType.FullName, fi.Name)); if (typeDoc != null) { if (typeDoc.SelectSingleNode(".//*[local-name() = 'summary']") != null) { writer.Write(typeDoc.SelectSingleNode(".//*[local-name() = 'summary']").InnerText.Replace("\r\n", "")); } } writer.WriteLine(); writer.WriteLine("\t */"); writer.WriteLine("\t{0} : '{1}',", fi.Name, fi.GetValue(null)); } writer.WriteLine("}} // {0} ", jobject.Id); }