public static string[] ExtractComments(string spanID, string itemID, Dictionary <string, HtmlDocument> docs, CommentParamExtractor paramExtractor = null) { var comments = new List <string>(); for (var node = docs.Values.Select(d => d.DocumentNode.SelectSingleNode($@"//span[@id = '{spanID}']")).Where(n => n != null).FirstOrDefault(); node != null; node = node.NextSibling == null || node.NextSibling.Name != "#text" ? null : node.NextSibling) { var lines = node.InnerText.Split(NEWLINES, StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).ToArray(); foreach (var line in lines) { if (comments.Count <= 0) // Search for BEGIN if EMPTY { var start = line.IndexOf("/**"); if (start < 0) { if (lines.Length > 1 || paramExtractor == null) { continue; } // If only 1 line without comments // Examples: // - "constructor: function (config) {" // - "boundSeries: []," // - "masterAxis: null," var sep = line.IndexOf(':'); if (sep < 0) { continue; } sep++; while (sep < line.Length && line[sep] == ' ') { sep++; } if (sep >= (line.Length - 1)) { continue; } var value = line.Substring(sep); if (value.StartsWith("function")) { var open = value.IndexOf('('); if (open < 0) { continue; } var close = value.IndexOf(')'); if (open < 0 || open >= (close - 1)) { continue; } foreach (var functionParam in value.Substring(open + 1, close - open - 1).Split(COMMAS, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim())) { var functionParam2 = functionParam; // /* private */ skipValidation if (functionParam2.StartsWith("/* ")) { var closing = functionParam2.IndexOf("*/"); if (closing > 0) { for (closing += 2; functionParam2[closing] == ' '; closing++) { ; } functionParam2 = functionParam2.Substring(closing); } } paramExtractor("param", $@"{{Object}} {functionParam2}", null); } } else { //var comma = value.IndexOf(','); //if (comma >= 0) // value = value.Substring(0, comma); continue; } } else { var commentLine = NormalizeComments(line.Substring(start)); // Continue search for ending if (commentLine.EndsWith("*/")) { // Only 1 comment line return new string[] { commentLine } } ; else { // 1st comment line, and continue to next BLOCK comments.Add(commentLine); } } } else { var commentLine = NormalizeComments(line); // If not ENDING: check for comment format from the follow lines if (!commentLine.EndsWith("*/")) { if (!commentLine.StartsWith("* @") || commentLine.Length <= 3) { comments.Add(' ' + commentLine); } else { var pos = commentLine.IndexOfAny(BLANKS, 3); string type, data; if (pos > 0) { type = commentLine.Substring(3, pos - 3); data = pos >= (commentLine.Length - 2) ? null : commentLine.Substring(pos + 1); } else { type = commentLine.Substring(3); data = null; } if (paramExtractor != null) { commentLine = paramExtractor(type, data, commentLine); } if (commentLine != null) { comments.Add(' ' + commentLine); } } } else { // Check for EMPTY comment if (commentLine == "*/" && (comments.ToString() == ("/**" + Environment.NewLine))) { return(null); } else { comments.Add(' ' + commentLine); var deletionCount = 0; for (var i = 1; i < comments.Count; i++) { if (comments[i] == " *") { deletionCount++; } else { break; } } if (deletionCount > 0) { comments.RemoveRange(1, deletionCount); } for (var i = comments.Count - 2; i >= 0; i--) { if (comments[i] == " *") { comments.RemoveAt(i); } else { break; } } return(comments.Count <= 2 ? null : comments.ToArray()); } } } } } return(null); }
public static string[] ExtractComments(string spanID, string itemID, Dictionary <string, HtmlDocument> docs, CommentParamExtractor paramExtractor = null) { var comments = new List <string>(); string commentSummary; for (var node = docs.Values.Select(d => d.DocumentNode.SelectSingleNode($@"//span[@id = '{spanID}']")).Where(n => n != null).FirstOrDefault(); node != null; node = node.NextSibling == null || node.NextSibling.Name != "#text" ? null : node.NextSibling) { var firstLine = true; commentSummary = string.Empty; var lines = node.InnerText.Split(NEWLINES, StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim()).ToArray(); foreach (var line in lines) { if (comments.Count <= 0) // Search for BEGIN if EMPTY { var start = line.IndexOf("/**"); if (start < 0) { if (lines.Length > 1 || paramExtractor == null) { continue; } // If only 1 line without comments // Examples: // - "constructor: function (config) {" // - "boundSeries: []," // - "masterAxis: null," var sep = line.IndexOf(':'); if (sep < 0) { continue; } sep++; while (sep < line.Length && line[sep] == ' ') { sep++; } if (sep >= (line.Length - 1)) { continue; } var value = line.Substring(sep); if (value.StartsWith("function")) { var open = value.IndexOf('('); if (open < 0) { continue; } var close = value.IndexOf(')'); if (open < 0 || open >= (close - 1)) { continue; } foreach (var functionParam in value.Substring(open + 1, close - open - 1).Split(COMMAS, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim())) { var functionParam2 = functionParam; // /* private */ skipValidation if (functionParam2.StartsWith("/* ")) { var closing = functionParam2.IndexOf("*/"); if (closing > 0) { for (closing += 2; functionParam2[closing] == ' '; closing++) { ; } functionParam2 = functionParam2.Substring(closing); } } paramExtractor("param", $@"{{Object}} {functionParam2}", null); } } else { //var comma = value.IndexOf(','); //if (comma >= 0) // value = value.Substring(0, comma); continue; } } else { var commentLine = NormalizeComments(line.Substring(start)); // Continue search for ending if (commentLine.EndsWith("*/")) { // Only 1 comment line return new string[] { commentLine } } ; else { // 1st comment line, and continue to next BLOCK comments.Add(commentLine); } } } else { var commentLine = NormalizeComments(line); // If not ENDING: check for comment format from the follow lines if (!commentLine.EndsWith("*/")) { if (!commentLine.StartsWith("* @") || commentLine.Length <= 3) { if (firstLine) { firstLine = false; commentSummary = commentLine; } comments.Add(' ' + commentLine); } else { var pos = commentLine.IndexOfAny(BLANKS, 3); string type, data; if (pos > 0) { type = commentLine.Substring(3, pos - 3); data = pos >= (commentLine.Length - 2) ? null : commentLine.Substring(pos + 1); } else { type = commentLine.Substring(3); data = null; } if (paramExtractor != null) { commentLine = paramExtractor(type, data, commentLine); } if (commentLine != null) { comments.Add(' ' + commentLine); } } } else { // Check for EMPTY comment if (commentLine == "*/" && (comments.ToString() == ("/**" + Environment.NewLine))) { return(null); } else { comments.Add(' ' + commentLine); var deletionCount = 0; for (var i = 1; i < comments.Count; i++) { if (comments[i] == " *") { deletionCount++; } else { break; } } if (deletionCount > 0) { comments.RemoveRange(1, deletionCount); } for (var i = comments.Count - 2; i >= 0; i--) { if (comments[i] == " *") { comments.RemoveAt(i); } else { break; } } // Use @summary and @description if the comment is more than some lines long if (!firstLine && !string.IsNullOrWhiteSpace(commentSummary) && comments.Count > (3 + 2)) { // Check also if the first @param line (if any) is after that 5-line-count threshold var firstAtParamLine = comments.FindIndex(l => l.StartsWith(" * @param")); if (firstAtParamLine < 0 || firstAtParamLine > (3 + 1)) { // Try to match just the first sentence of the string summary. if (commentSummary.Contains(". ")) { // Adds 1 to the index just so that we grab the period from the string. commentSummary = commentSummary.Substring(0, commentSummary.IndexOf(". ") + 1); } // Try up to three lines below to find the end of the sentence if the first line does not conclude a sentence. if (!commentSummary.EndsWith(".")) { var i = 0; for (; i < 3; i++) // warning: 3 here shouldn't be more than 3 (see comments.Count > (3+2) above!) { if (comments[i + 2].Contains(". ")) { // '2' in substring start index is to skip ' *' from begin // index ends with -1 cause it already starts at 2, then we move one // position back to finish right in the period. commentSummary += comments[i + 2].Substring(2, comments[i + 2].IndexOf(". ") - 1); break; // sentence completed, no need to continue searching for the end. } else if (comments[i + 2].EndsWith(".")) { commentSummary += comments[i + 2].Substring(2); break; // sentence completed, no need to continue searching for the end. } else if (comments[i + 2].Length <= 3) { // An empty line, then the previous sentence must have ended. Stop looking for the end of the sentence // and return with an unmodified code. break; } else { // The whole line is still continuing the sentence, so just append it. commentSummary += comments[i + 2].Substring(2); } } // At this point, if it didn't get to the end of the comment's first sentence, we'll leave it unterminated and expect // the user will check the comment's @description body. So we add a '...' to the end of the sentence. if (i >= 3 && !commentSummary.EndsWith(".")) { commentSummary += "..."; } } comments.Insert(1, " * @summary"); comments.Insert(2, ' ' + commentSummary); comments.Insert(3, " * @description"); } } return(comments.Count <= 2 ? null : comments.ToArray()); } } } } } return(null); }