/// <summary> /// Checks summary element. /// </summary> /// <param name="nodeKind">Kind of node.</param> /// <param name="xmlElement">The XML element.</param> /// <param name="location">Location.</param> /// <param name="message">Message.</param> /// <returns>True if there is no error.</returns> private static bool CheckSummaryElement(SyntaxKind nodeKind, XmlElementSyntax xmlElement, ref Location location, ref string message) { // Check xml start/end tags if (!BTAnalyzer.CheckXmlTags(xmlElement, ref location, ref message)) { return(false); } // Set location location = xmlElement.StartTag.GetLocation(); Position position = Position.Origin; // Check XML element text string[] lines = xmlElement.ToString().Split(new string[] { "///" }, StringSplitOptions.None); // Check int offset = lines[0].Length + 3; for (int i = 1; i < lines.Length; i++) { string trimmedLine = lines[i].TrimEnd(' ', '\r', '\n'); if (' ' != trimmedLine[0]) { message = ErrorCode.MissingSpace; location = BTAnalyzer.GetLocation(xmlElement.SyntaxTree, location, new Position(0, 0), offset); return(false); } // Ignore first and last lines if ((0 < i) && (i < lines.Length - 1)) { // Validate text foreach (StringValidator.Validate validate in BTAnalyzer.GetSummaryValidator(nodeKind)) { if (!validate(trimmedLine, ref message, ref position)) { location = BTAnalyzer.GetLocation(xmlElement.SyntaxTree, location, position, offset); return(false); } } } // Increase offset offset += lines[i].Length + 3; } // Return true return(true); }
/// <summary> /// Checks param element. /// </summary> /// <param name="xmlElement">XML element.</param> /// <param name="location">Location.</param> /// <param name="message">Message.</param> /// <param name="paramCommentNameList">Parameter comment name list.</param> /// <returns>True if there is no error.</returns> private static bool CheckParamElement(XmlElementSyntax xmlElement, ref Location location, ref string message, ref List <string> paramCommentNameList) { // Check tags if (!BTAnalyzer.CheckXmlTags(xmlElement, ref location, ref message)) { return(false); } // Set location location = xmlElement.StartTag.GetLocation(); Position position = Position.Origin; // Add param name to the list XmlNameAttributeSyntax xmlNameAttribute = xmlElement.StartTag.Attributes.Where(attr => SyntaxKind.XmlNameAttribute == attr.Kind()).FirstOrDefault() as XmlNameAttributeSyntax; if (null == xmlNameAttribute) { message = ErrorCode.MissingNameAttribute; return(false); } paramCommentNameList.Add(xmlNameAttribute.Identifier.ToString()); // Remove <see cref .. /> elements, remove start and end tags // Check XML element text string text = xmlElement.ToString().Replace(xmlElement.StartTag.ToString(), String.Empty).Replace(xmlElement.EndTag.ToString(), String.Empty).TrimStart('/'); foreach (StringValidator.Validate validate in BTAnalyzer.ParamTextValidators) { if (!validate(text, ref message, ref position)) { location = BTAnalyzer.GetLocation(xmlElement.SyntaxTree, location, position, xmlElement.StartTag.ToString().Length); return(false); } } // Return true return(true); }
/// <summary> /// Analyzes comment inside block. /// </summary> /// <param name="context">Context.</param> private static void AnalyzeInsideMethodComment(SyntaxNodeAnalysisContext context) { // Get the block MethodDeclarationSyntax methodDeclarationSyntax = context.Node as MethodDeclarationSyntax; if (null == methodDeclarationSyntax) { return; } // Get all single line comment trivia IEnumerable <SyntaxTrivia> singleLineCommentTrivias = methodDeclarationSyntax.Body.DescendantTrivia().Where(trivia => (SyntaxKind.SingleLineCommentTrivia == trivia.Kind()) || (SyntaxKind.SingleLineDocumentationCommentTrivia == trivia.Kind())); // Iterate through each single line comment string message = string.Empty; foreach (SyntaxTrivia singleLineComment in singleLineCommentTrivias) { Location location = singleLineComment.GetLocation(); Position position = Position.Origin; if (!StringValidator.StartWithTwoSlashes(singleLineComment.ToString(), ref message, ref position)) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, BTAnalyzer.GetLocation(singleLineComment.SyntaxTree, location, position), message)); continue; } string trimmedText = singleLineComment.ToString().Substring(3); foreach (StringValidator.Validate validate in BTAnalyzer.NormalCommentValidator) { if (!validate(trimmedText, ref message, ref position)) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, BTAnalyzer.GetLocation(singleLineComment.SyntaxTree, location, position, 3), message)); } } } }
/// <summary> /// Checks returns element. /// </summary> /// <param name="xmlElement">XML element.</param> /// <param name="location">Location.</param> /// <param name="message">Message.</param> /// <param name="returnCount">Return count.</param> /// <returns>True if valid.</returns> private static bool CheckReturnElement(XmlElementSyntax xmlElement, ref Location location, ref string message, ref int returnCount) { // Check tags if (!BTAnalyzer.CheckXmlTags(xmlElement, ref location, ref message)) { return(false); } // Increment number of <returns> returnCount++; // Set location location = xmlElement.StartTag.GetLocation(); Position position = Position.Origin; // Check if (xmlElement.ToString().Contains("\r\n")) { string[] lines = xmlElement.ToString().Split(new string[] { "///" }, StringSplitOptions.None); int offset = lines[0].Length + 3; for (int i = 1; i < lines.Length; i++) { string trimmedLine = lines[i].TrimEnd(' ', '\r', '\n'); if (' ' != trimmedLine[0]) { message = ErrorCode.MissingSpace; location = BTAnalyzer.GetLocation(xmlElement.SyntaxTree, location, new Position(0, 0), offset); return(false); } // Ignore first and last line if ((0 < i) && (i < lines.Length - 1)) { // Validate text foreach (StringValidator.Validate validate in BTAnalyzer.ReturnTextValidators) { if (!validate(trimmedLine, ref message, ref position)) { location = BTAnalyzer.GetLocation(xmlElement.SyntaxTree, location, position, offset); return(false); } } } // Add offset, 3 for the removed /// offset += lines[i].Length + 3; } } else { string text = xmlElement.ToString().Replace(xmlElement.StartTag.ToString(), string.Empty).Replace(xmlElement.EndTag.ToString(), string.Empty); string trimmedLine = text.TrimEnd(' ', '\r', '\n'); foreach (StringValidator.Validate validate in BTAnalyzer.ReturnTextValidators) { if (!validate(trimmedLine, ref message, ref position)) { location = BTAnalyzer.GetLocation(xmlElement.SyntaxTree, location, position, xmlElement.StartTag.ToString().Length); return(false); } } } // Return true return(true); }
/// <summary> /// Analyzes empty lines inside blocks. /// </summary> /// <param name="context">Context.</param> private static void AnalyzeBlockEmptyLines(SyntaxNodeAnalysisContext context) { // Return early if (BTAnalyzer.IsGenerated(context)) { return; } // Get the block ClassDeclarationSyntax classDeclarationSyntax = context.Node as ClassDeclarationSyntax; // Check method spacing SyntaxNode[] nodes = classDeclarationSyntax.ChildNodes().Where(node => SyntaxKind.BaseList != node.Kind() && SyntaxKind.AttributeList != node.Kind()).ToArray(); Location location = default(Location); for (int i = 0; i < nodes.Length; i++) { SyntaxNode method = nodes[i]; IEnumerable <SyntaxTrivia> trivias = method.GetFirstToken().LeadingTrivia; int count = 0; int j = 0; foreach (SyntaxTrivia trivia in trivias) { if (j == 0) { location = BTAnalyzer.GetLocation(trivia.SyntaxTree, trivia.GetLocation(), Position.Origin); j++; } if (SyntaxKind.EndOfLineTrivia == trivia.Kind()) { count++; } else if (!(SyntaxKind.WhitespaceTrivia == trivia.Kind())) { break; } } if ((i == 0 && count > 0) || (i > 0 && count > 1)) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, location, ErrorCode.ExtraLine)); } if (i > 0 && count < 1) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, location, ErrorCode.MissingEmptyLine)); } } // Check blocks IEnumerable <BlockSyntax> blocks = classDeclarationSyntax.DescendantNodes().Where(node => SyntaxKind.Block == node.Kind()).OfType <BlockSyntax>(); // Iterate through each block foreach (BlockSyntax block in blocks) { // Get a list of node, single line comment and end of line List <Tuple <object, SyntaxKind> > blockObjectList = new List <Tuple <object, SyntaxKind> >(); foreach (SyntaxNode node in block.ChildNodes()) { // Check whether the node is a block node bool isBlockNode = node.ChildNodes().Any(nodee => SyntaxKind.Block == nodee.Kind()); // Get all trivias SyntaxTrivia[] allTrivias = node.DescendantTrivia().Where(trivia => (SyntaxKind.SingleLineCommentTrivia == trivia.Kind()) || (SyntaxKind.EndOfLineTrivia == trivia.Kind())).ToArray(); // Add trivias before the node foreach (SyntaxTrivia trivia in allTrivias) { if (trivia.Span.End <= node.SpanStart) { blockObjectList.Add(Tuple.Create <object, SyntaxKind>(trivia, trivia.Kind())); } } // Add the node blockObjectList.Add(Tuple.Create <object, SyntaxKind>(node, SyntaxKind.None)); // Add trivia after the node foreach (SyntaxTrivia trivia in allTrivias) { if ((trivia.SpanStart >= node.Span.End) && !isBlockNode) { blockObjectList.Add(Tuple.Create <object, SyntaxKind>(trivia, trivia.Kind())); } } // Add an additional end of line trivia for block node if (isBlockNode) { blockObjectList.Add(Tuple.Create <object, SyntaxKind>(null, SyntaxKind.EndOfLineTrivia)); } } // Trim the block object list // Remove end of line after a statement List <Tuple <object, SyntaxKind> > trimmedBlockObjectList = new List <Tuple <object, SyntaxKind> >(); for (int i = 0; i < blockObjectList.Count(); i++) { if (SyntaxKind.EndOfLineTrivia != blockObjectList[i].Item2) { if ((i + 1 < blockObjectList.Count()) && (SyntaxKind.EndOfLineTrivia == blockObjectList[i + 1].Item2)) { trimmedBlockObjectList.Add(blockObjectList[i]); i++; continue; } } trimmedBlockObjectList.Add(blockObjectList[i]); } // Cannot start with empty lines for (int i = 0; i < trimmedBlockObjectList.Count(); i++) { if (SyntaxKind.EndOfLineTrivia == blockObjectList[i].Item2) { SyntaxTrivia trivia = (SyntaxTrivia)trimmedBlockObjectList[i].Item1; context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, trivia.GetLocation(), ErrorCode.ExtraLine)); } else { break; } } // Check if there is only syntax node inside the block bool isOnlySyntaxNode = trimmedBlockObjectList.All(tuple => SyntaxKind.None == tuple.Item2); // Statement should have comments before for (int i = 0; i < trimmedBlockObjectList.Count(); i++) { // Skip nodes while ((i < trimmedBlockObjectList.Count()) && (SyntaxKind.None != trimmedBlockObjectList[i].Item2)) { i++; } // Check for missing comment if (i < trimmedBlockObjectList.Count()) { if ((((0 < i - 1) && (SyntaxKind.SingleLineCommentTrivia != trimmedBlockObjectList[i - 1].Item2)) || i == 0) && !isOnlySyntaxNode) { SyntaxNode node = (SyntaxNode)trimmedBlockObjectList[i].Item1; context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, Location.Create(node.SyntaxTree, new TextSpan(node.SpanStart, 10)), ErrorCode.MissingComment)); } } while ((i < trimmedBlockObjectList.Count()) && (SyntaxKind.None == trimmedBlockObjectList[i].Item2)) { i++; } // Check for missing empty line if ((i < trimmedBlockObjectList.Count()) && (SyntaxKind.EndOfLineTrivia != trimmedBlockObjectList[i].Item2)) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, ((SyntaxTrivia)trimmedBlockObjectList[i].Item1).GetLocation(), ErrorCode.MissingEmptyLine)); } } // Cannot end with empty lines IEnumerable <SyntaxTrivia> triviaBeforeClosingBrackets = block.CloseBraceToken.LeadingTrivia; if (1 < triviaBeforeClosingBrackets.Count()) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, block.CloseBraceToken.GetLocation(), ErrorCode.UnexpectedComponentsBeforeClosingBracket)); } } }