/// <summary> /// Analyzes class declaration. /// </summary> /// <param name="context">Syntax node analysis context.</param> private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) { if (BTAnalyzer.IsGenerated(context)) { return; } BTAnalyzer.AnalyzeSummaryComments(context); }
/// <summary> /// Analyzes items inside methods. /// </summary> /// <param name="context">Code block analysis context.</param> private static void AnalyzeInsideMethod(SyntaxNodeAnalysisContext context) { if (BTAnalyzer.IsGenerated(context)) { return; } // Analyze comments inside methods BTAnalyzer.AnalyzeInsideMethodComment(context); BTAnalyzer.AnalyzeLogicalExpressions(context); }
/// <summary> /// Analyzes equal comments. /// </summary> /// <param name="context">Context.</param> private static void AnalyzeLogicalExpressions(SyntaxNodeAnalysisContext context) { // Get method declaration node MethodDeclarationSyntax methodDeclaration = context.Node as MethodDeclarationSyntax; if (null == methodDeclaration) { return; } // Get all equal family expressions // Check that constants are on the left IEnumerable <SyntaxNode> equalFamilyExpressions = methodDeclaration.DescendantNodes().Where(node => BTAnalyzer.IsEqualFamilyExpression(node.Kind()) && (SyntaxKind.ForStatement != node.Parent.Kind())); foreach (SyntaxNode equalNode in equalFamilyExpressions) { SyntaxNode[] twoSideExpressionNodes = equalNode.ChildNodes().ToArray(); if (2 != twoSideExpressionNodes.Count()) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, equalNode.GetLocation(), ErrorCode.InvalidExpression)); continue; } if (!BTAnalyzer.IsConstant(twoSideExpressionNodes[0]) && BTAnalyzer.IsConstant(twoSideExpressionNodes[1])) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, equalNode.GetLocation(), ErrorCode.ConstantOnLeft)); } } // Get all logical expressions // Check that full parenthization is used in logical expressions // IEnumerable<SyntaxNode> logicalExpressions = methodDeclaration.DescendantNodes().Where(node => BTAnalyzer.IsLogicalExpression(node.Kind())); // foreach (SyntaxNode logicalNode in logicalExpressions) // { // foreach (SyntaxNode node in logicalNode.ChildNodes().Where(node => BTAnalyzer.IsEqualFamilyExpression(node.Kind()))) // context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, node.GetLocation(), ErrorCode.MissingFullParenthization)); // } // Check that statements with 1 expression statement should ignore parenthesis IEnumerable <SyntaxNode> blockStatements = methodDeclaration.DescendantNodes().Where(node => BTAnalyzer.IsBlockStatement(node.Kind())); foreach (SyntaxNode node in blockStatements) { BlockSyntax block = node.ChildNodes().Where(nodee => SyntaxKind.Block == nodee.Kind()).OfType <BlockSyntax>().FirstOrDefault(); if (null == block) { continue; } IEnumerable <SyntaxNode> expressionNodes = block.ChildNodes().Where(nodee => BTAnalyzer.IsSingleLineStatement(nodee)); if ((1 == expressionNodes.Count()) && (1 == block.ChildNodes().Count())) { context.ReportDiagnostic(Diagnostic.Create(BTAnalyzer.Rule, node.GetLocation(), ErrorCode.UnnecessaryBlock)); } } }
private static async Task <Document> MakeConstantLeft(Document document, SyntaxNode node, CancellationToken cancellationToken) { // Sometimes the equal node is the child of the node passed in if (!BTAnalyzer.IsEqualFamilyExpression(node.Kind())) { node = node.ChildNodes().FirstOrDefault(); } // Get child nodes if (null == node) { return(null); } // Get child node SyntaxNode[] childNodes = node.ChildNodes().ToArray(); if (2 != childNodes.Count()) { return(null); } // Replace the old local declaration with the new local declaration var oldRoot = await document.GetSyntaxRootAsync(cancellationToken); SyntaxNode newNode0 = childNodes[0].ReplaceToken(childNodes[0].GetFirstToken(), SyntaxFactory.ParseToken(childNodes[0].GetFirstToken().ToString().Trim())); SyntaxNode newNode1 = childNodes[1].ReplaceToken(childNodes[1].GetFirstToken(), SyntaxFactory.ParseToken(childNodes[1].GetFirstToken().ToString() + " ")); SyntaxNode newRoot = null; SyntaxNode newNode = null; try { newNode = node.ReplaceNodes(childNodes, (original, _) => original == childNodes[0] ? newNode1 : newNode0); SyntaxToken token = newNode.ChildTokens().FirstOrDefault(); if (null == token) { return(null); } if (BTCodeFixProvider.GreaterDictionary.ContainsKey(token.Kind())) { newNode = newNode.ReplaceToken(token, SyntaxFactory.ParseToken(SyntaxFactory.Token(BTCodeFixProvider.GreaterDictionary[token.Kind()]).ToString() + " ")); } newRoot = oldRoot.ReplaceNode(node, newNode); } catch (Exception e) { } // Return document with transformed tree return(document.WithSyntaxRoot(newRoot)); }
/// <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> /// Checks XML start and end tags. /// </summary> /// <param name="xmlElement">XML element.</param> /// <param name="location">Location.</param> /// <param name="message">Message.</param> /// <returns>True if valid.</returns> private static bool CheckXmlTags(XmlElementSyntax xmlElement, ref Location location, ref string message) { // Start/End tag string startTag = xmlElement.StartTag.Name.ToString(); string endTag = xmlElement.EndTag.Name.ToString(); if (string.IsNullOrWhiteSpace(startTag) || string.IsNullOrWhiteSpace(endTag)) { message = ErrorCode.ErrorsInComment; location = xmlElement.GetLocation(); return(false); } // Only allows summary or param tag if (!BTAnalyzer.IsValidXmlCommentTag(startTag) || !BTAnalyzer.IsValidXmlCommentTag(endTag)) { message = ErrorCode.InvalidXmlTag; location = xmlElement.GetLocation(); 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> /// Checks documentation comment trivia. /// </summary> /// <param name="commentTria">Comment trivia.</param> /// <param name="nodeKind">Kind of node.</param> /// <param name="messageList">Message list.</param> /// <param name="locationList">Location list.</param> /// <param name="paramCommentNameList">Parameter comment name list.</param> /// <param name="returnCount">Return count.</param> /// <returns>Ture if there is no error.</returns> private static bool CheckDocumentationCommentTrivia(SyntaxTrivia commentTria, SyntaxKind nodeKind, ref List <string> messageList, ref List <Location> locationList, ref List <string> paramCommentNameList, ref int returnCount) { // Preset result to true bool result = true; // Return early if the trivia has no structure if (!commentTria.HasStructure) { messageList.Add(ErrorCode.ClassCommentStart); return(false); } // Check XML elements SyntaxNode commentNode = commentTria.GetStructure(); IEnumerable <XmlElementSyntax> xmlElements = commentNode.DescendantNodes().Where(node => typeof(XmlElementSyntax) == node.GetType()).OfType <XmlElementSyntax>(); // Return false if no XML element is found if (0 == xmlElements.Count()) { messageList.Add(ErrorCode.ErrorsInComment); return(false); } // Check each XML element Location location = commentTria.GetLocation(); string message = string.Empty; foreach (XmlElementSyntax xmlElement in xmlElements) { // Swtich switch (xmlElement.StartTag.Name.ToString()) { case "summary": { if (!BTAnalyzer.CheckSummaryElement(nodeKind, xmlElement, ref location, ref message)) { result = false; messageList.Add(message); locationList.Add(location); } break; } case "param": { if (!BTAnalyzer.CheckParamElement(xmlElement, ref location, ref message, ref paramCommentNameList)) { result = false; messageList.Add(message); locationList.Add(location); } break; } case "returns": { if (!BTAnalyzer.CheckReturnElement(xmlElement, ref location, ref message, ref returnCount)) { result = false; messageList.Add(message); locationList.Add(location); } break; } } } // Return true return(result); }
/// <summary> /// Analyzes class comments. /// </summary> /// <param name="context">Context.</param> private static void AnalyzeSummaryComments(SyntaxNodeAnalysisContext context) { // Get declaration node SyntaxNode declarationNode = context.Node; if (null == declarationNode) { return; } // Check all trivia under the first token SyntaxToken?firstToken = declarationNode?.DescendantTokens().FirstOrDefault(); // No single line comment IEnumerable <SyntaxTrivia> singleLineCommentTrivias = firstToken?.GetAllTrivia().Where(trivia => SyntaxKind.SingleLineCommentTrivia == trivia.Kind()); if (0 < singleLineCommentTrivias.Count()) { BTAnalyzer.ReportDiagnostic(context, singleLineCommentTrivias.First().GetLocation(), ErrorCode.ClassCommentStart); return; } // Only 1 single documentation line comment IEnumerable <SyntaxTrivia> singleDocumentationCommentTrivias = firstToken?.GetAllTrivia().Where(trivia => SyntaxKind.SingleLineDocumentationCommentTrivia == trivia.Kind()); if (0 == singleDocumentationCommentTrivias.Count()) { BTAnalyzer.ReportDiagnostic(context, declarationNode.GetFirstToken().GetLocation(), ErrorCode.MissingComment); return; } else if (1 < singleDocumentationCommentTrivias.Count()) { BTAnalyzer.ReportDiagnostic(context, declarationNode.GetFirstToken().GetLocation(), ErrorCode.ErrorsInComment); return; } // Get the only single line documentation comment trivia SyntaxTrivia singleDocCommentTrivia = singleDocumentationCommentTrivias.First(); List <string> messageList = new List <string>(); List <Location> locationList = new List <Location>(); List <string> paramCommentNameList = new List <string>(); int returnCount = 0; if (!BTAnalyzer.CheckDocumentationCommentTrivia(singleDocCommentTrivia, declarationNode.Kind(), ref messageList, ref locationList, ref paramCommentNameList, ref returnCount)) { for (int i = 0; i < messageList.Count(); i++) { BTAnalyzer.ReportDiagnostic(context, locationList[i], messageList[i]); } } // Check method declaration // Check parameter comments MethodDeclarationSyntax methodDeclarationSyntax = declarationNode as MethodDeclarationSyntax; if (null == methodDeclarationSyntax) { return; } // Check parameter comment count IEnumerable <ParameterSyntax> parameters = methodDeclarationSyntax.ParameterList.Parameters; if (paramCommentNameList.Count() != parameters.Count()) { BTAnalyzer.ReportDiagnostic(context, methodDeclarationSyntax.ParameterList.GetLocation(), ErrorCode.IncompatibleParamComments); } // Check that all parameters have comment foreach (ParameterSyntax parameter in parameters) { if (!paramCommentNameList.Any(p => p == parameter.Identifier.Text)) { BTAnalyzer.ReportDiagnostic(context, parameter.GetLocation(), ErrorCode.MissingParamComment); break; } } // Check number of returns comments if ("void" == methodDeclarationSyntax.ReturnType.ToString()) { if (0 < returnCount) { BTAnalyzer.ReportDiagnostic(context, methodDeclarationSyntax.ReturnType.GetLocation(), ErrorCode.IncorrectReturnComment); return; } } else { if (1 != returnCount) { BTAnalyzer.ReportDiagnostic(context, methodDeclarationSyntax.ReturnType.GetLocation(), ErrorCode.IncorrectReturnComment); return; } } }
/// <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)); } } }