/// <summary> /// Checks the built-in types and empty strings within a document. /// </summary> /// <param name="document"> /// The document containing the tokens. /// </param> /// <param name="settings"> /// The current settings. /// </param> private void IterateTokenList(CsDocument document, Settings settings) { Param.AssertNotNull(document, "document"); Param.Ignore(settings); for (Node <CsToken> tokenNode = document.Tokens.First; tokenNode != null; tokenNode = tokenNode.Next) { CsToken token = tokenNode.Value; if (token.CsTokenClass == CsTokenClass.Type || token.CsTokenClass == CsTokenClass.GenericType) { // Check that the type is using the built-in types, if applicable. this.CheckBuiltInType(tokenNode, document); if (token.CsTokenClass == CsTokenClass.GenericType) { this.CheckShorthandForNullableTypes(tokenNode.Value); } } else if (token.CsTokenType == CsTokenType.String) { // Check that the string is not using the empty string "" syntax. this.CheckEmptyString(tokenNode); } else if (token.CsTokenClass == CsTokenClass.RegionDirective && settings.DoNotUseRegions) { Region region = (Region)token; if (region.Beginning && !region.Generated && !region.IsGeneratedCodeRegion) { // There should not be any regions in the code. this.AddViolation(token.FindParentElement(), token.LineNumber, Rules.DoNotUseRegions); } } else if (settings.AvoidStringFormatUseStringInterpolation && token.CsTokenType == CsTokenType.Other && token.Text == "Format") { // Check this rule only if project target framework version is greater or equal 4.6 if (document.SourceCode.Project.TargetFrameworkVersion >= 4.6) { MemberAccessExpression expression = token.Parent?.Parent as MemberAccessExpression; // Check if literal expression is not null and check text to avoid user's custom code like enumeration. if (expression != null && expression.Text == "string.Format") { CsToken region = (CsToken)token; if (!region.Generated) { // There should not be any regions in the code. this.AddViolation(token.FindParentElement(), token.LineNumber, Rules.AvoidStringFormatUseStringInterpolation); } } } } } }
/// <summary> /// Checks a string to determine whether it is using an incorrect empty string notation. /// </summary> /// <param name="stringNode"> /// The node containing the string to check. /// </param> private void CheckEmptyString(Node <CsToken> stringNode) { Param.AssertNotNull(stringNode, "stringNode"); CsToken @string = stringNode.Value; Debug.Assert(@string.CsTokenType == CsTokenType.String, "The token must be a string."); if (string.Equals(@string.Text, "\"\"", StringComparison.Ordinal) || string.Equals(@string.Text, "@\"\"", StringComparison.Ordinal) || string.Equals(@string.Text, "$\"\"", StringComparison.Ordinal)) { // Look at the previous non-whitespace token. If it is the 'case' keyword, then do not throw this // exception. It is illegal to write case String.Empty : and instead case "": must be written. // We also check that the node is not part of a method parameter as these must be "" also. Node <CsToken> previousToken = null; for (Node <CsToken> previousNode = stringNode.Previous; previousNode != null; previousNode = previousNode.Previous) { if (previousNode.Value.CsTokenType != CsTokenType.WhiteSpace && previousNode.Value.CsTokenType != CsTokenType.EndOfLine && previousNode.Value.CsTokenType != CsTokenType.SingleLineComment && previousNode.Value.CsTokenType != CsTokenType.MultiLineComment) { previousToken = previousNode; break; } } if (previousToken == null || (previousToken.Value.CsTokenType != CsTokenType.Case && !IsConstVariableDeclaration(previousToken) && !IsMethodParameterDeclaration(previousToken))) { this.AddViolation(@string.FindParentElement(), @string.LineNumber, Rules.UseStringEmptyForEmptyStrings); } } }
/// <summary> /// Checks the built-in types and empty strings within a document. /// </summary> /// <param name="document"> /// The document containing the tokens. /// </param> /// <param name="settings"> /// The current settings. /// </param> private void IterateTokenList(CsDocument document, Settings settings) { Param.AssertNotNull(document, "document"); Param.Ignore(settings); for (Node <CsToken> tokenNode = document.Tokens.First; tokenNode != null; tokenNode = tokenNode.Next) { CsToken token = tokenNode.Value; if (token.CsTokenClass == CsTokenClass.Type || token.CsTokenClass == CsTokenClass.GenericType) { // Check that the type is using the built-in types, if applicable. this.CheckBuiltInType(tokenNode, document); if (token.CsTokenClass == CsTokenClass.GenericType) { this.CheckShorthandForNullableTypes(tokenNode.Value); } } else if (token.CsTokenType == CsTokenType.String) { // Check that the string is not using the empty string "" syntax. this.CheckEmptyString(tokenNode); } else if (token.CsTokenClass == CsTokenClass.RegionDirective && settings.DoNotUseRegions) { Region region = (Region)token; if (region.Beginning && !region.Generated && !region.IsGeneratedCodeRegion) { // There should not be any regions in the code. this.AddViolation(token.FindParentElement(), token.LineNumber, Rules.DoNotUseRegions); } } } }
/// <summary> /// Checks for tabs in the given comment. /// </summary> /// <param name="comment"> /// The comment token. /// </param> private void CheckTabsInComment(CsToken comment) { Param.AssertNotNull(comment, "comment"); int lineEnds = 0; for (int i = 0; i < comment.Text.Length; ++i) { if (comment.Text[i] == '\t') { this.AddViolation(comment.FindParentElement(), comment.Location, Rules.TabsMustNotBeUsed); } else if (comment.Text[i] == '\n') { ++lineEnds; } } }
/// <summary> /// Checks to make sure that preprocessor type keyword is not preceded by a space. /// </summary> /// <param name="preprocessor"> /// The preprocessor token. /// </param> private void CheckPreprocessorSpacing(CsToken preprocessor) { Param.AssertNotNull(preprocessor, "preprocessor"); if (preprocessor.Text.Length > 1) { if (preprocessor.Text[0] == '#') { if (preprocessor.Text[1] == ' ' || preprocessor.Text[1] == '\t') { this.AddViolation(preprocessor.FindParentElement(), preprocessor.Location, Rules.PreprocessorKeywordsMustNotBePrecededBySpace); } } } }
private void CheckLineSpacingNonWhitespace( CsDocument document, Node<CsToken> precedingTokenNode, CsToken token, bool fileHeader, bool firstTokenOnLine, int count) { Param.AssertNotNull(document, "document"); Param.Ignore(precedingTokenNode); Param.AssertNotNull(token, "token"); Param.Ignore(fileHeader); Param.Ignore(firstTokenOnLine); Param.AssertGreaterThanOrEqualToZero(count, "count"); // Skip generated tokens. if (!token.Generated) { // If there is at least one blank line in front of the current token. if (count > 1) { if (token.CsTokenType == CsTokenType.CloseCurlyBracket) { // The blank line is just before a closing curly bracket. this.AddViolation(token.FindParentElement(), token.LineNumber, Rules.ClosingCurlyBracketsMustNotBePrecededByBlankLine); } else if (token.CsTokenType == CsTokenType.OpenCurlyBracket) { bool throwViolation = false; // The blank line is just before an opening curly bracket. // If next element back is if,while,catch,try,finally,do,else,lock,switch,unsafe, using, array init, delegate, it's a violation. if (precedingTokenNode == null) { throwViolation = true; } else { Statement parentStatement = precedingTokenNode.Value.FindParentStatement(); if (parentStatement == null) { throwViolation = true; } else { StatementType statementType = parentStatement.StatementType; if (statementType == StatementType.If || statementType == StatementType.While || statementType == StatementType.Catch || statementType == StatementType.Try || statementType == StatementType.Finally || statementType == StatementType.DoWhile || statementType == StatementType.Else || statementType == StatementType.Lock || statementType == StatementType.Switch || statementType == StatementType.Unsafe || statementType == StatementType.Using) { throwViolation = true; } else if (statementType == StatementType.VariableDeclaration && precedingTokenNode.Value.CsTokenType != CsTokenType.Semicolon) { throwViolation = true; } else if (statementType == StatementType.Expression && precedingTokenNode.Value.CsTokenType == CsTokenType.Delegate) { throwViolation = true; } } } if (throwViolation) { this.AddViolation(token.FindParentElement(), token.Location, Rules.OpeningCurlyBracketsMustNotBePrecededByBlankLine); } } else if (token.CsTokenType == CsTokenType.Else || token.CsTokenType == CsTokenType.Catch || token.CsTokenType == CsTokenType.Finally) { // The blank line is just before an else, catch, or finally statement. this.AddViolation(token.FindParentElement(), token.LineNumber, Rules.ChainedStatementBlocksMustNotBePrecededByBlankLine); } else if (token.CsTokenType == CsTokenType.WhileDo) { // The blank line is just before the while keyword from a do/while statement. this.AddViolation(token.FindParentElement(), token.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 && precedingTokenNode != null && precedingTokenNode.Value.CsTokenType == CsTokenType.SingleLineComment && token.CsTokenType != CsTokenType.SingleLineComment && token.CsTokenType != CsTokenType.MultiLineComment && token.CsTokenType != CsTokenType.XmlHeader) { // 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 tokenSeen = false; if (precedingTokenNode != null) { foreach (CsToken lineToken in document.Tokens.ReverseIterator(precedingTokenNode.Previous)) { if (lineToken.CsTokenType == CsTokenType.EndOfLine) { break; } else if (lineToken.CsTokenType != CsTokenType.WhiteSpace) { tokenSeen = 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 = precedingTokenNode.Value.Text.Trim(); if (!tokenSeen && !trimmedComment.StartsWith(@"////", StringComparison.Ordinal)) { // The blank line appears after a file header, we want to allow this. if (!IsCommentInFileHeader(precedingTokenNode)) { this.AddViolation( precedingTokenNode.Value.FindParentElement(), precedingTokenNode.Value.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 (token.CsTokenType == CsTokenType.XmlHeader) { // This is a violation unless the previous line contains another // Xml header, an opening curly bracket, or a preprocessor directive. if (precedingTokenNode != null && precedingTokenNode.Value.CsTokenType != CsTokenType.XmlHeader && precedingTokenNode.Value.CsTokenType != CsTokenType.OpenCurlyBracket && precedingTokenNode.Value.CsTokenType != CsTokenType.PreprocessorDirective) { this.AddViolation(token.FindParentElement(), token.LineNumber, Rules.ElementDocumentationHeaderMustBePrecededByBlankLine); } } else if (token.CsTokenType == CsTokenType.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 (precedingTokenNode != null && precedingTokenNode.Value.CsTokenType != CsTokenType.SingleLineComment && precedingTokenNode.Value.CsTokenType != CsTokenType.OpenCurlyBracket && precedingTokenNode.Value.CsTokenType != CsTokenType.LabelColon && precedingTokenNode.Value.CsTokenType != CsTokenType.PreprocessorDirective) { // 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 = token.Text.Trim(); if (!trimmedComment.StartsWith(@"////", StringComparison.Ordinal) && !Utils.IsAReSharperComment(token)) { CsElement element = token.FindParentElement(); if (element != null) { this.AddViolation(element, token.LineNumber, Rules.SingleLineCommentMustBePrecededByBlankLine); } } } } else if (precedingTokenNode != null && precedingTokenNode.Value.CsTokenType == CsTokenType.CloseCurlyBracket) { // Closing curly brackets cannot be followed directly by another bracket keyword. Bracket closingCurlyBracket = precedingTokenNode.Value as Bracket; if (closingCurlyBracket.MatchingBracket != null && closingCurlyBracket.MatchingBracket.LineNumber != closingCurlyBracket.LineNumber && firstTokenOnLine && token.CsTokenType != CsTokenType.CloseCurlyBracket && token.CsTokenType != CsTokenType.Finally && token.CsTokenType != CsTokenType.Catch && token.CsTokenType != CsTokenType.WhileDo && token.CsTokenType != CsTokenType.Else && token.CsTokenType != CsTokenType.PreprocessorDirective && token.CsTokenType != CsTokenType.Select && token.CsTokenType != CsTokenType.From && token.CsTokenType != CsTokenType.Let && token.CsTokenType != CsTokenType.OperatorSymbol && token.CsTokenType != CsTokenType.By) { this.AddViolation(closingCurlyBracket.FindParentElement(), closingCurlyBracket.LineNumber, Rules.ClosingCurlyBracketMustBeFollowedByBlankLine); } } } } }