Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity.Design / System / Data / EntityModel / Emitters / CommentEmitter.cs
1 //---------------------------------------------------------------------
2 // <copyright file="CommentEmitter.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
10 using System;
11 using System.CodeDom;
12 using System.Diagnostics;
13 using System.Text.RegularExpressions;
14 using System.Data;
15 using System.Data.Common.Utils;
16 using System.Data.EntityModel.SchemaObjectModel;
17 using System.Globalization;
18 using System.Data.Entity.Design.Common;
19 using System.Data.Entity.Design;
20 using System.Data.Metadata.Edm;
21 using System.Reflection;
22 using System.Xml;
23 using System.IO;
24
25
26 namespace System.Data.EntityModel.Emitters
27 {
28     /// <summary>
29     /// static helper class for emitting comments.
30     /// </summary>
31     internal static class CommentEmitter
32     {
33         #region Static Fields
34         private static readonly Regex LeadingBlanks = new Regex(@"^(?<LeadingBlanks>\s{1,})\S", RegexOptions.Singleline | RegexOptions.Compiled);
35         #endregion
36
37         #region Public Methods
38         /// <summary>
39         /// emit all the documentation comments for an element's documentation child
40         /// (if the element does not have a documentation child emit some standard "missing comments" comment
41         /// </summary>
42         /// <param name="element">the element whose documentation is to be displayed</param>
43         /// <param name="commentCollection">the comment collection of the CodeDom object to be commented</param>
44         public static void EmitSummaryComments(MetadataItem item, CodeCommentStatementCollection commentCollection)
45         {
46             Debug.Assert(item != null, "item parameter is null");
47             Debug.Assert(commentCollection != null, "commentCollection parameter is null");
48
49             Documentation documentation = GetDocumentation(item);
50             string [] summaryComments = null;
51             if (documentation != null && !MetadataUtil.IsNullOrEmptyOrWhiteSpace(documentation.Summary)) 
52             {
53                 // we have documentation to emit
54                 summaryComments = GetFormattedLines(documentation.Summary, true);
55             }
56             else
57             {
58                 string summaryComment;
59                 // no summary content, so use a default
60                 switch (item.BuiltInTypeKind)
61                 {
62                     case BuiltInTypeKind.EdmProperty:
63                         summaryComment = Strings.MissingPropertyDocumentation(((EdmProperty)item).Name);
64                         break;
65                     case BuiltInTypeKind.ComplexType:
66                         summaryComment = Strings.MissingComplexTypeDocumentation(((ComplexType)item).FullName);
67                         break;
68                     default:
69                         {
70                             PropertyInfo pi = item.GetType().GetProperty("FullName");
71                             if (pi == null)
72                             {
73                                 pi = item.GetType().GetProperty("Name");
74                             }
75
76                             object value = null;
77                             if (pi != null)
78                             {
79                                 value = pi.GetValue(item, null);
80                             }
81
82
83                             if (value != null)
84                             {
85                                 summaryComment = Strings.MissingDocumentation(value.ToString());
86                             }
87                             else
88                             {
89                                 summaryComment = Strings.MissingDocumentationNoName;
90                             }
91                         }
92                         break;
93                 }
94                 summaryComments = new string[] { summaryComment };
95             }
96             EmitSummaryComments(summaryComments, commentCollection);
97             EmitOtherDocumentationComments(documentation, commentCollection);
98         }
99
100         private static Documentation GetDocumentation(MetadataItem item)
101         {
102             if (item is Documentation)
103                 return (Documentation)item;
104             else
105                 return item.Documentation;
106         }
107
108         /// <summary>
109         /// Emit summary comments from a string
110         /// </summary>
111         /// <param name="summaryComments">the summary comments to be emitted</param>
112         /// <param name="commentCollection">the comment collection of the CodeDom object to be commented</param>
113         public static void EmitSummaryComments(string summaryComments, CodeCommentStatementCollection commentCollection)
114         {
115             Debug.Assert(commentCollection != null, "commentCollection parameter is null");
116
117             if (string.IsNullOrEmpty(summaryComments) || string.IsNullOrEmpty(summaryComments = summaryComments.TrimEnd()))
118                 return;
119
120             EmitSummaryComments(SplitIntoLines(summaryComments), commentCollection);
121         }
122
123         /// <summary>
124         /// Emit some lines of comments
125         /// </summary>
126         /// <param name="commentLines">the lines of comments to emit</param>
127         /// <param name="commentCollection">the comment collection of the CodeDom object to be commented</param>
128         /// <param name="docComment">true if the comments are 'documentation' comments</param>
129         public static void EmitComments(string[] commentLines, CodeCommentStatementCollection commentCollection, bool docComment)
130         {
131             Debug.Assert(commentLines != null, "commentLines parameter is null");
132             Debug.Assert(commentCollection != null, "commentCollection parameter is null");
133
134             foreach (string comment in commentLines)
135             {
136                 commentCollection.Add(new CodeCommentStatement(comment, docComment));
137             }
138         }
139
140         /// <summary>
141         /// Emit documentation comments for a method parameter
142         /// </summary>
143         /// <param name="parameter">the parameter being commented</param>
144         /// <param name="comment">the comment text</param>
145         /// <param name="commentCollection">the comment collection of the CodeDom object to be commented</param>
146         public static void EmitParamComments(CodeParameterDeclarationExpression parameter, string comment,
147             CodeCommentStatementCollection commentCollection)
148         {
149             Debug.Assert(parameter != null, "parameter parameter is null");
150             Debug.Assert(comment != null, "comment parameter is null");
151
152             string paramComment = string.Format(System.Globalization.CultureInfo.CurrentCulture,
153                 "<param name=\"{0}\">{1}</param>", parameter.Name, comment);
154             commentCollection.Add(new CodeCommentStatement(paramComment, true));
155         }
156
157         /// <summary>
158         /// 'Format' a string of text into lines: separates in to lines on '\n', removes '\r', and removes common leading blanks.
159         /// </summary>
160         /// <param name="escapeForXml">if true characters troublesome for xml are converted to entities</param>
161         /// <param name="text">the text to be formatted</param>
162         /// <returns>the formatted lines</returns>
163         public static string[] GetFormattedLines(string text, bool escapeForXml)
164         {
165 #if false
166             if ( text.IndexOf("\n") >= 0 )
167                 Console.WriteLine("GetFormattedText(\""+text.Replace("\n","\\n").Replace("\r","\\r")+"\","+escapeForXml+")");
168 #endif
169             Debug.Assert(!string.IsNullOrEmpty(text));
170
171             // nothing in, almost nothing out.
172             if (StringUtil.IsNullOrEmptyOrWhiteSpace(text))
173                 return new string[] { "" };
174
175             // normalize CRLF and LFCRs to LFs (we just remove all the crs, assuming there are no extraneous ones) and remove trailing spaces
176             text = text.Replace("\r", "");
177
178             // remove leading and.or trailing line ends to get single line for:
179             // <documentation>
180             // text
181             // <documentation>
182             bool trim = false;
183             int start = text.IndexOf('\n');
184             if (start >= 0 && MetadataUtil.IsNullOrEmptyOrWhiteSpace(text, 0, start + 1))
185             {
186                 ++start;
187                 trim = true;
188             }
189             else
190             {
191                 start = 0;
192             }
193             int last = text.LastIndexOf('\n');
194             if (last > start - 1 && MetadataUtil.IsNullOrEmptyOrWhiteSpace(text, last))
195             {
196                 --last;
197                 trim = true;
198             }
199             else
200             {
201                 last = text.Length - 1;
202             }
203             if (trim)
204             {
205                 Debug.Assert(start <= last);
206                 text = text.Substring(start, last - start + 1);
207             }
208
209             // break into lines (preversing blank lines and preping text for being in xml comments)
210             if (escapeForXml)
211                 text = MetadataUtil.Entityize(text);
212             string[] lines = SplitIntoLines(text);
213
214             if (lines.Length == 1)
215             {
216                 lines[0] = lines[0].Trim();
217                 return lines;
218             }
219
220             // find the maximum leading whitespace substring (ignoring blank lines)
221             string leadingBlanks = null;
222             foreach (string line in lines)
223             {
224                 // is an empty line
225                 if (MetadataUtil.IsNullOrEmptyOrWhiteSpace(line))
226                     continue;
227
228                 // find the leading whitespace substring
229                 Match match = LeadingBlanks.Match(line);
230                 if (!match.Success)
231                 {
232                     //none, we're done
233                     leadingBlanks = "";
234                     break;
235                 }
236
237                 if (leadingBlanks == null)
238                 {
239                     // this is first non-empty line
240                     leadingBlanks = match.Groups["LeadingBlanks"].Value;
241                     continue;
242                 }
243
244                 // use the leadingBlanks if it matched the new one or it is a leading substring of the new one
245                 string leadingBlanks2 = match.Groups["LeadingBlanks"].Value;
246                 if (leadingBlanks2 == leadingBlanks || leadingBlanks2.StartsWith(leadingBlanks, StringComparison.Ordinal))
247                     continue;
248
249                 if (leadingBlanks.StartsWith(leadingBlanks2, StringComparison.OrdinalIgnoreCase))
250                 {
251                     // the current leading whitespace string is a leading substring of leadingBlanks. use the new one
252                     leadingBlanks = leadingBlanks2;
253                     continue;
254                 }
255
256                 // find longest leading common substring and use that.
257                 int minLength = Math.Min(leadingBlanks.Length, leadingBlanks2.Length);
258                 for (int j = 0; j < minLength; ++j)
259                 {
260                     if (leadingBlanks[j] != leadingBlanks2[j])
261                     {
262                         if (j == 0)
263                             leadingBlanks = "";
264                         else
265                             leadingBlanks = leadingBlanks.Substring(0, j);
266                         break;
267                     }
268                 }
269
270                 // if we've reduced the leading substring to an empty string, we're done.
271                 if (string.IsNullOrEmpty(leadingBlanks))
272                     break;
273             }
274
275             // remove the leading whitespace substring and remove any trailing blanks.
276             int numLeadingCharsToRemove = leadingBlanks.Length;
277             for (int i = 0; i < lines.Length; ++i)
278             {
279                 if (lines[i].Length >= numLeadingCharsToRemove)
280                     lines[i] = lines[i].Substring(numLeadingCharsToRemove);
281                 lines[i] = lines[i].TrimEnd();
282             }
283             return lines;
284         }
285         #endregion
286
287         #region Private Methods
288         /// <summary>
289         /// Emit the other (than Summary) documentation comments from a Documentation element
290         /// </summary>
291         /// <param name="documentation">the schema Docuementation element</param>
292         /// <param name="commentCollection">the comment collection of the CodeDom object to be commented</param>
293         private static void EmitOtherDocumentationComments(Documentation documentation, CodeCommentStatementCollection commentCollection)
294         {
295             Debug.Assert(commentCollection != null);
296             if (documentation == null)
297                 return;
298
299             if (!string.IsNullOrEmpty(documentation.LongDescription))
300                 EmitXmlComments("LongDescription", GetFormattedLines(documentation.LongDescription, true), commentCollection);
301         }
302
303         /// <summary>
304         /// Emit the summary comments
305         /// </summary>
306         /// <param name="summaryComments"></param>
307         /// <param name="commentCollection">the comment collection of the CodeDom object to be commented</param>
308         private static void EmitSummaryComments(string[] summaryComments, CodeCommentStatementCollection commentCollection)
309         {
310             Debug.Assert(summaryComments != null);
311             Debug.Assert(commentCollection != null);
312
313             EmitXmlComments("summary", summaryComments, commentCollection);
314         }
315
316         /// <summary>
317         /// emit documentation comments between xml open and close tags
318         /// </summary>
319         /// <param name="tag">the xml tag name</param>
320         /// <param name="summaryComments">the lines of comments to emit</param>
321         /// <param name="commentCollection">the comment collection of the CodeDom object to be commented</param>
322         private static void EmitXmlComments(string tag, string[] summaryComments, CodeCommentStatementCollection commentCollection)
323         {
324             Debug.Assert(tag != null);
325             Debug.Assert(summaryComments != null);
326             Debug.Assert(commentCollection != null);
327
328             commentCollection.Add(new CodeCommentStatement(string.Format(CultureInfo.InvariantCulture, "<{0}>", tag), true));
329             EmitComments(summaryComments, commentCollection, true);
330             commentCollection.Add(new CodeCommentStatement(string.Format(CultureInfo.InvariantCulture, "</{0}>", tag), true));
331         }
332
333         /// <summary>
334         /// split a string into lines on '\n' chars and remove '\r' chars 
335         /// </summary>
336         /// <param name="text">the string to split</param>
337         /// <returns>the split string</returns>
338         private static string[] SplitIntoLines(string text)
339         {
340             if (string.IsNullOrEmpty(text))
341                 return new string[] { "" };
342
343             return text.Replace("\r", "").Split('\n');
344         }
345         #endregion
346     }
347 }