/// <summary> /// Determines whether the given token is part of a file header. It is considered /// part of a file header if the only tokens in front of it are single-line comments, /// whitespace, or newlines. /// </summary> /// <param name="comment">The comment to check.</param> /// <returns>Returns true if the comment is part of a file header.</returns> private static bool IsCommentInFileHeader(Comment comment) { Param.AssertNotNull(comment, "comment"); LexicalElement item = comment; while (item != null) { if (!item.Is(CommentType.SingleLineComment) && item.LexicalElementType != LexicalElementType.WhiteSpace && item.LexicalElementType != LexicalElementType.EndOfLine) { return(false); } item = item.FindPreviousLexicalElement(); } return(true); }
/// <summary> /// Determines whether the given bracket is the only thing on its line or whether it shares the line. /// </summary> /// <param name="bracketNode">The bracket to check.</param> /// <param name="allowTrailingCharacters">Indicates whether a semicolon, comma or closing parenthesis after the /// bracket is allowed.</param> /// <returns>Returns true if the bracket shares the line with something else.</returns> private static bool BracketSharesLine(Token bracketNode, bool allowTrailingCharacters) { Param.AssertNotNull(bracketNode, "bracketNode"); Param.Ignore(allowTrailingCharacters); // Look forward. bool sharesLine = false; // Find the next non-whitespace or comment item. LexicalElement nextItem = null; for (LexicalElement item = bracketNode.FindNextLexicalElement(); item != null; item = item.FindNextLexicalElement()) { if (item.LexicalElementType == LexicalElementType.EndOfLine) { break; } else if (item.LexicalElementType != LexicalElementType.WhiteSpace && item.LexicalElementType != LexicalElementType.Comment) { nextItem = item; break; } } if (nextItem != null) { if (!allowTrailingCharacters || (!nextItem.Is(TokenType.Semicolon) && !nextItem.Is(TokenType.Comma) && !nextItem.Is(TokenType.CloseParenthesis) && !nextItem.Is(TokenType.CloseSquareBracket))) { sharesLine = true; } } if (!sharesLine) { // Look backwards. for (LexicalElement item = bracketNode.FindPreviousLexicalElement(); item != null; item = item.FindPreviousLexicalElement()) { if (item.LexicalElementType == LexicalElementType.EndOfLine) { break; } else if (item.LexicalElementType != LexicalElementType.WhiteSpace && !item.Is(CommentType.SingleLineComment)) { sharesLine = true; break; } } } return(sharesLine); }
private void CheckLineSpacingNonWhitespace( CsDocument document, LexicalElement precedingItem, LexicalElement item, bool fileHeader, bool firstTokenOnLine, int count) { Param.AssertNotNull(document, "document"); Param.Ignore(precedingItem); Param.AssertNotNull(item, "item"); Param.Ignore(fileHeader); Param.Ignore(firstTokenOnLine); Param.AssertGreaterThanOrEqualToZero(count, "count"); // Skip generated tokens. if (!item.Generated) { // If there is at least one blank line in front of the current token. if (count > 1) { if (item.Is(TokenType.CloseCurlyBracket)) { // The blank line is just before a closing curly bracket. this.AddViolation( item.FindParentElement(), item.LineNumber, Rules.ClosingCurlyBracketsMustNotBePrecededByBlankLine); } else if (item.Is(TokenType.OpenCurlyBracket)) { // The blank line is just before an opening curly bracket. this.AddViolation( item.FindParentElement(), item.LineNumber, Rules.OpeningCurlyBracketsMustNotBePrecededByBlankLine); } else if (item.Is(TokenType.Else) || item.Is(TokenType.Catch) || item.Is(TokenType.Finally)) { // The blank line is just before an else, catch, or finally statement. this.AddViolation( item.FindParentElement(), item.LineNumber, Rules.ChainedStatementBlocksMustNotBePrecededByBlankLine); } else if (item.Is(TokenType.WhileDo)) { // The blank line is just before the while keyword from a do/while statement. this.AddViolation( item.FindParentElement(), item.LineNumber, Rules.WhileDoFooterMustNotBePrecededByBlankLine); } // Check if there is a blank line after a single-line comment. The exceptions // are if this is the file header, or if the line after the blank line contains // another comment or an Xml header. This is ok if the comment is not // the first item on its line. if (!fileHeader && precedingItem != null && precedingItem.Is(CommentType.SingleLineComment) && !item.Is(LexicalElementType.Comment) && !IsXmlHeader(item)) { Comment precedingComment = (Comment)precedingItem; // Now check whether the comment is the first item on its line. If the comment // is not the first item on the line, then this is not a violation. bool itemSeen = false; for (LexicalElement lineItem = precedingComment.FindPreviousLexicalElement(); lineItem != null; lineItem = lineItem.FindPreviousLexicalElement()) { if (lineItem.LexicalElementType == LexicalElementType.EndOfLine) { break; } else if (lineItem.LexicalElementType != LexicalElementType.WhiteSpace) { itemSeen = true; break; } } // Now make sure this comment does not begin with '////'. If so, this is the signal // that StyleCop should ignore this particular error. This is used when the // developer is commenting out a line of code. In this case it is not a true comment. string trimmedComment = precedingComment.Text.Trim(); if (!itemSeen && !trimmedComment.StartsWith(@"////", StringComparison.Ordinal)) { // The blank line appears after a file header, we want to allow this. if (!IsCommentInFileHeader(precedingComment)) { this.AddViolation( precedingComment.FindParentElement(), precedingComment.LineNumber, Rules.SingleLineCommentsMustNotBeFollowedByBlankLine); } } } } else if (count == 1) { // There is one line return in front of the current token, which means // the line in front of the current token is not blank. Check if the current // token is an Xml header. if (IsXmlHeader(item)) { // This is a violation unless the previous line contains another // Xml header, an opening curly bracket, or a preprocessor directive. if (precedingItem != null && !IsXmlHeader(precedingItem) && !precedingItem.Is(TokenType.OpenCurlyBracket) && !IsPreprocessorDirective(precedingItem)) { this.AddViolation( item.FindParentElement(), item.LineNumber, Rules.ElementDocumentationHeaderMustBePrecededByBlankLine); } } else if (item.Is(CommentType.SingleLineComment)) { // The current line contains a single line comment and the previous line // is not blank. This is a violation unless the previous line contains // another single line comment, an opening curly bracket, a preprocessor // directive, or if the last character on the previous line is a colon, // which can only mean that it is a label or a case statement, in which // case this is ok. if (precedingItem != null && !precedingItem.Is(CommentType.SingleLineComment) && !precedingItem.Is(TokenType.OpenCurlyBracket) && !precedingItem.Is(TokenType.LabelColon) && !IsPreprocessorDirective(precedingItem)) { // Now make sure this comment does not begin with '////'. If so, this is the signal // that StyleCop should ignore this particular error. This is used when the // developer is commenting out a line of code. In this case it is not a true comment. string trimmedComment = item.Text.Trim(); if (!trimmedComment.StartsWith(@"////", StringComparison.Ordinal)) { this.AddViolation( item.FindParentElement(), item.LineNumber, Rules.SingleLineCommentMustBePrecededByBlankLine); } } } if (precedingItem != null && precedingItem.Is(TokenType.CloseCurlyBracket)) { // Closing curly brackets cannot be followed directly by another bracket keyword. CloseCurlyBracketToken closingCurlyBracket = (CloseCurlyBracketToken)precedingItem; if (closingCurlyBracket.MatchingBracket != null && closingCurlyBracket.MatchingBracket.LineNumber != closingCurlyBracket.LineNumber && firstTokenOnLine && !item.Is(TokenType.CloseCurlyBracket) && !item.Is(TokenType.Finally) && !item.Is(TokenType.Catch) && !item.Is(TokenType.WhileDo) && !item.Is(TokenType.Else) && !IsPreprocessorDirective(item)) { this.AddViolation( closingCurlyBracket.FindParentElement(), closingCurlyBracket.LineNumber, Rules.ClosingCurlyBracketMustBeFollowedByBlankLine); } } } } }
/// <summary> /// Checks a single-line comment to see if it is empty. /// </summary> /// <param name="comment">The comment.</param> /// <param name="parentElement">The parent element.</param> private void CheckSingleLineComment(Comment comment, Element parentElement) { Param.AssertNotNull(comment, "comment"); Param.AssertNotNull(parentElement, "parentElement"); // This is a single line comment. int slashCount = 0; bool found = false; // Loop through the characters in the comment text. for (int character = 0; character < comment.Text.Length; ++character) { // See if we've found the slashes at the beginning of the comment if (slashCount == 2) { // Check whether there's anything here other than whitespace. // If so, then this comment is ok. if (comment.Text[character] != ' ' && comment.Text[character] != '\t' && comment.Text[character] != '\r' && comment.Text[character] != '\n') { found = true; break; } } else if (comment.Text[character] == '/') { ++slashCount; } } // Check whether the comment contained any text. if (!found) { // This is only allowed if this comment is sandwiched in between two other comments. bool isComment = false; int lines = 0; for (LexicalElement item = comment.FindPreviousLexicalElement(); item != null; item = item.FindPreviousLexicalElement()) { if (item.Text == "\n") { ++lines; if (lines > 1) { break; } } else if (item.Is(CommentType.SingleLineComment)) { isComment = true; break; } else if (item.LexicalElementType != LexicalElementType.WhiteSpace) { break; } } if (!isComment) { this.AddViolation(parentElement, comment.LineNumber, Rules.CommentsMustContainText); } else { isComment = false; lines = 0; for (LexicalElement item = comment.FindNextLexicalElement(); item != null; item = item.FindNextLexicalElement()) { if (item.Text == "\n") { ++lines; if (lines > 1) { break; } } else if (item.Is(CommentType.SingleLineComment)) { isComment = true; break; } else if (item.LexicalElementType != LexicalElementType.WhiteSpace) { break; } } if (!isComment) { this.AddViolation(parentElement, comment.LineNumber, Rules.CommentsMustContainText); } } } }
/// <summary> /// Gets the non-whitespace item that appears before the given item. /// </summary> /// <param name="item">The original item.</param> /// <returns>Returns the previous item.</returns> private static LexicalElement GetPreviousNonWhitespaceItem(CodeUnit item) { Param.AssertNotNull(item, "item"); for (LexicalElement previous = item.FindPreviousLexicalElement(); previous != null; previous = previous.FindPreviousLexicalElement()) { if (!previous.Is(LexicalElementType.EndOfLine) && !previous.Is(LexicalElementType.WhiteSpace)) { return(previous); } } return(null); }
/// <summary> /// Checks a type to determine whether it should use one of the built-in types. /// </summary> /// <param name="type">The type to check.</param> /// <param name="parentElement">The parent element.</param> private void CheckBuiltInType(TypeToken type, Element parentElement) { Param.AssertNotNull(type, "type"); Param.AssertNotNull(parentElement, "parentElement"); if (!type.IsGeneric) { for (int i = 0; i < this.builtInTypes.Length; ++i) { string[] builtInType = this.builtInTypes[i]; if (type.MatchTokens(builtInType[ShortNameIndex]) || type.MatchTokens("System", ".", builtInType[ShortNameIndex])) { // If the previous token is an equals sign, then this is a using alias directive. For example: // using SomeAlias = System.String; bool usingAliasDirective = false; for (LexicalElement previous = type.FindPreviousLexicalElement(); previous != null; previous = previous.FindPreviousLexicalElement()) { if (previous.LexicalElementType != LexicalElementType.Comment && previous.LexicalElementType != LexicalElementType.WhiteSpace && previous.LexicalElementType != LexicalElementType.EndOfLine) { if (previous.Text == "=") { usingAliasDirective = true; } break; } } if (!usingAliasDirective) { this.Violation( Rules.UseBuiltInTypeAlias, new ViolationContext(parentElement, type.LineNumber, builtInType[AliasIndex], builtInType[ShortNameIndex], builtInType[LongNameIndex]), (c, o) => { // Insert a new type token with the correct aliased version of the type. CsDocument document = type.Document; TypeToken aliasType = document.CreateTypeToken(document.CreateLiteralToken(builtInType[AliasIndex])); document.Replace(type, aliasType); }); } break; } } } }