1 //---------------------------------------------------------------------
2 // <copyright file="CommentEmitter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
12 using System.Diagnostics;
13 using System.Text.RegularExpressions;
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;
26 namespace System.Data.EntityModel.Emitters
29 /// static helper class for emitting comments.
31 internal static class CommentEmitter
34 private static readonly Regex LeadingBlanks = new Regex(@"^(?<LeadingBlanks>\s{1,})\S", RegexOptions.Singleline | RegexOptions.Compiled);
37 #region Public Methods
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
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)
46 Debug.Assert(item != null, "item parameter is null");
47 Debug.Assert(commentCollection != null, "commentCollection parameter is null");
49 Documentation documentation = GetDocumentation(item);
50 string [] summaryComments = null;
51 if (documentation != null && !MetadataUtil.IsNullOrEmptyOrWhiteSpace(documentation.Summary))
53 // we have documentation to emit
54 summaryComments = GetFormattedLines(documentation.Summary, true);
58 string summaryComment;
59 // no summary content, so use a default
60 switch (item.BuiltInTypeKind)
62 case BuiltInTypeKind.EdmProperty:
63 summaryComment = Strings.MissingPropertyDocumentation(((EdmProperty)item).Name);
65 case BuiltInTypeKind.ComplexType:
66 summaryComment = Strings.MissingComplexTypeDocumentation(((ComplexType)item).FullName);
70 PropertyInfo pi = item.GetType().GetProperty("FullName");
73 pi = item.GetType().GetProperty("Name");
79 value = pi.GetValue(item, null);
85 summaryComment = Strings.MissingDocumentation(value.ToString());
89 summaryComment = Strings.MissingDocumentationNoName;
94 summaryComments = new string[] { summaryComment };
96 EmitSummaryComments(summaryComments, commentCollection);
97 EmitOtherDocumentationComments(documentation, commentCollection);
100 private static Documentation GetDocumentation(MetadataItem item)
102 if (item is Documentation)
103 return (Documentation)item;
105 return item.Documentation;
109 /// Emit summary comments from a string
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)
115 Debug.Assert(commentCollection != null, "commentCollection parameter is null");
117 if (string.IsNullOrEmpty(summaryComments) || string.IsNullOrEmpty(summaryComments = summaryComments.TrimEnd()))
120 EmitSummaryComments(SplitIntoLines(summaryComments), commentCollection);
124 /// Emit some lines of comments
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)
131 Debug.Assert(commentLines != null, "commentLines parameter is null");
132 Debug.Assert(commentCollection != null, "commentCollection parameter is null");
134 foreach (string comment in commentLines)
136 commentCollection.Add(new CodeCommentStatement(comment, docComment));
141 /// Emit documentation comments for a method parameter
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)
149 Debug.Assert(parameter != null, "parameter parameter is null");
150 Debug.Assert(comment != null, "comment parameter is null");
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));
158 /// 'Format' a string of text into lines: separates in to lines on '\n', removes '\r', and removes common leading blanks.
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)
166 if ( text.IndexOf("\n") >= 0 )
167 Console.WriteLine("GetFormattedText(\""+text.Replace("\n","\\n").Replace("\r","\\r")+"\","+escapeForXml+")");
169 Debug.Assert(!string.IsNullOrEmpty(text));
171 // nothing in, almost nothing out.
172 if (StringUtil.IsNullOrEmptyOrWhiteSpace(text))
173 return new string[] { "" };
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", "");
178 // remove leading and.or trailing line ends to get single line for:
183 int start = text.IndexOf('\n');
184 if (start >= 0 && MetadataUtil.IsNullOrEmptyOrWhiteSpace(text, 0, start + 1))
193 int last = text.LastIndexOf('\n');
194 if (last > start - 1 && MetadataUtil.IsNullOrEmptyOrWhiteSpace(text, last))
201 last = text.Length - 1;
205 Debug.Assert(start <= last);
206 text = text.Substring(start, last - start + 1);
209 // break into lines (preversing blank lines and preping text for being in xml comments)
211 text = MetadataUtil.Entityize(text);
212 string[] lines = SplitIntoLines(text);
214 if (lines.Length == 1)
216 lines[0] = lines[0].Trim();
220 // find the maximum leading whitespace substring (ignoring blank lines)
221 string leadingBlanks = null;
222 foreach (string line in lines)
225 if (MetadataUtil.IsNullOrEmptyOrWhiteSpace(line))
228 // find the leading whitespace substring
229 Match match = LeadingBlanks.Match(line);
237 if (leadingBlanks == null)
239 // this is first non-empty line
240 leadingBlanks = match.Groups["LeadingBlanks"].Value;
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))
249 if (leadingBlanks.StartsWith(leadingBlanks2, StringComparison.OrdinalIgnoreCase))
251 // the current leading whitespace string is a leading substring of leadingBlanks. use the new one
252 leadingBlanks = leadingBlanks2;
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)
260 if (leadingBlanks[j] != leadingBlanks2[j])
265 leadingBlanks = leadingBlanks.Substring(0, j);
270 // if we've reduced the leading substring to an empty string, we're done.
271 if (string.IsNullOrEmpty(leadingBlanks))
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)
279 if (lines[i].Length >= numLeadingCharsToRemove)
280 lines[i] = lines[i].Substring(numLeadingCharsToRemove);
281 lines[i] = lines[i].TrimEnd();
287 #region Private Methods
289 /// Emit the other (than Summary) documentation comments from a Documentation element
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)
295 Debug.Assert(commentCollection != null);
296 if (documentation == null)
299 if (!string.IsNullOrEmpty(documentation.LongDescription))
300 EmitXmlComments("LongDescription", GetFormattedLines(documentation.LongDescription, true), commentCollection);
304 /// Emit the summary comments
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)
310 Debug.Assert(summaryComments != null);
311 Debug.Assert(commentCollection != null);
313 EmitXmlComments("summary", summaryComments, commentCollection);
317 /// emit documentation comments between xml open and close tags
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)
324 Debug.Assert(tag != null);
325 Debug.Assert(summaryComments != null);
326 Debug.Assert(commentCollection != null);
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));
334 /// split a string into lines on '\n' chars and remove '\r' chars
336 /// <param name="text">the string to split</param>
337 /// <returns>the split string</returns>
338 private static string[] SplitIntoLines(string text)
340 if (string.IsNullOrEmpty(text))
341 return new string[] { "" };
343 return text.Replace("\r", "").Split('\n');