private void ProcessCodeUnit(CodeUnit item, XmlNode parentNode) { XmlNode elementNode = RecordItem(item, parentNode); for (CodeUnit child = item.Children.First; child != null; child = child.LinkNode.Next) { ProcessCodeUnit(child, elementNode); } }
/// <summary> /// Initializes a new instance of the LocalFunctionStatement class. /// </summary> /// <param name="tokens"> /// The list of tokens that form the statement. /// </param> /// <param name="returnType"> /// The return type of this local function. /// </param> /// <param name="returnTypeIsRef"> /// The return type of this local function is ref. /// </param> /// <param name="identifier"> /// The identifier of this local function. /// </param> /// <param name="parameters"> /// The parameter information of this local function. /// </param> /// <param name="functionBodyExpression"> /// An expression that represents the body of this local function. /// </param> public LocalFunctionStatement( CsTokenList tokens, TypeToken returnType, bool returnTypeIsRef, LiteralExpression identifier, IList <Parameter> parameters, Expression functionBodyExpression) : this(tokens, returnType, returnTypeIsRef, identifier, parameters) { Param.AssertNotNull(functionBodyExpression, nameof(functionBodyExpression)); this.functionBody = functionBodyExpression; this.AddExpression(functionBodyExpression); }
/// <summary> /// Initializes a new instance of the LocalFunctionStatement class. /// </summary> /// <param name="tokens"> /// The list of tokens that form the statement. /// </param> /// <param name="returnType"> /// The return type of this local function. /// </param> /// <param name="returnTypeIsRef"> /// The return type of this local function is ref. /// </param> /// <param name="identifier"> /// The identifier of this local function. /// </param> /// <param name="parameters"> /// The parameter information of this local function. /// </param> /// <param name="typeConstraints"> /// The list of type constraints on the element. /// </param> /// <param name="functionBodyExpression"> /// An expression that represents the body of this local function. /// </param> internal LocalFunctionStatement( CsTokenList tokens, TypeToken returnType, bool returnTypeIsRef, LiteralExpression identifier, IList <Parameter> parameters, ICollection <TypeParameterConstraintClause> typeConstraints, Expression functionBodyExpression) : this(tokens, returnType, returnTypeIsRef, identifier, parameters, typeConstraints) { Param.AssertNotNull(functionBodyExpression, nameof(functionBodyExpression)); this.functionBody = functionBodyExpression; this.AddExpression(functionBodyExpression); }
/// <summary> /// Determines the amount of offset to add to the line number of the next argument /// for a comment or attribute. /// </summary> /// <param name="item">The starting item.</param> /// <returns>Returns the amount of offset to add.</returns> private static int ParameterPrewordOffset(CodeUnit item) { Param.AssertNotNull(item, "tokenNode"); Debug.Assert(item.Is(CodeUnitType.Attribute) || item.Is(LexicalElementType.Comment), "The item must be an attribute or a comment."); // Find the start of the next argument. for (CodeUnit next = item.FindLast().FindNext(); next != null; next = next.FindNext()) { if (next.Is(LexicalElementType.EndOfLine)) { return item.Location.LineSpan; } else if (!next.Is(LexicalElementType.WhiteSpace) && !next.Is(LexicalElementType.WhiteSpace) && !next.Is(CodeUnitType.Attribute)) { return Math.Max(0, next.Location.StartPoint.LineNumber - item.Location.StartPoint.LineNumber); } } return 0; }
/// <summary> /// Visits one code unit in the document. /// </summary> /// <param name="codeUnit">The item being visited.</param> /// <param name="parentElement">The parent element, if any.</param> /// <param name="parentStatement">The parent statement, if any.</param> /// <param name="parentExpression">The parent expression, if any.</param> /// <param name="parentClause">The parent query clause, if any.</param> /// <param name="parentToken">The parent token, if any.</param> /// <param name="settings">The settings.</param> /// <returns>Returns true to continue, or false to stop the walker.</returns> private bool VisitCodeUnit( CodeUnit codeUnit, Element parentElement, Statement parentStatement, Expression parentExpression, QueryClause parentClause, Token parentToken, Settings settings) { Param.AssertNotNull(codeUnit, "codeUnit"); Param.Ignore(parentElement, parentStatement, parentExpression, parentClause, parentToken); Param.AssertNotNull(settings, "settings"); if (codeUnit.CodeUnitType == CodeUnitType.Element) { return this.VisitElement((Element)codeUnit, settings); } else if (codeUnit.CodeUnitType == CodeUnitType.Expression) { return this.VisitExpression((Expression)codeUnit, parentElement); } else if (codeUnit.Is(LexicalElementType.Token)) { Token token = (Token)codeUnit; if (token.TokenType == TokenType.Type && !token.Parent.Is(TokenType.Type)) { // Check that the type is using the built-in types, if applicable. this.CheckBuiltInType((TypeToken)token, parentElement); } else if (token.TokenType == TokenType.String) { // Check that the string is not using the empty string "" syntax. this.CheckEmptyString(token, parentElement); } } else if (codeUnit.Is(PreprocessorType.Region)) { this.CheckRegion((RegionDirective)codeUnit, parentElement, settings); } else if (codeUnit.Is(LexicalElementType.Comment)) { this.CheckForEmptyComments((Comment)codeUnit, parentElement); } return !this.Cancel; }
/// <summary> /// Analyzes the given query clauses. /// </summary> /// <param name="element">The element containing the clauses.</param> /// <param name="expression">The expression containing the clauses.</param> /// <param name="clauseParent">The direct parent of the collection of clauses to analyze.</param> /// <param name="previousClause">The previous clause in the expression, if any.</param> /// <param name="clauseOnSameLine">Indicates whether any clause has been seen previously which /// starts on the same line as the clause before it.</param> /// <param name="clauseOnSeparateLine">Indicates whether any clause has been seen previously which /// starts on the line after the clause before it.</param> /// <returns>Returns true to continue checking the query clause, or false to quit.</returns> private bool ProcessQueryClauses( Element element, QueryExpression expression, CodeUnit clauseParent, ref QueryClause previousClause, ref bool clauseOnSameLine, ref bool clauseOnSeparateLine) { Param.AssertNotNull(element, "element"); Param.AssertNotNull(expression, "expression"); Param.AssertNotNull(clauseParent, "clauseParent"); Param.Ignore(previousClause); Param.Ignore(clauseOnSameLine); Param.Ignore(clauseOnSeparateLine); for (QueryClause clause = clauseParent.FindFirstChildQueryClause(); clause != null; clause = clause.FindNextSiblingQueryClause()) { if (previousClause != null) { // Figure out the line number that the previous clause ends on. For most // clauses, this is simply the end point of the clause location property, // but for continuation clauses we want to use the location of the 'into' variable, // which conceptually represents the end of the continuation line. int previousClauseEndLineNumber = previousClause.Location.EndPoint.LineNumber; if (previousClause.QueryClauseType == QueryClauseType.Continuation) { previousClauseEndLineNumber = ((QueryContinuationClause)previousClause).Variable.Location.LineNumber; } // Ensure that the clause either starts on the same line as the expression, or // on the very next line. if (clause.LineNumber == previousClauseEndLineNumber) { // This is only ok if the previous clause does not span multiple lines. if (previousClause.Location.LineSpan > 1) { this.AddViolation(element, clause.LineNumber, Rules.QueryClauseMustBeginOnNewLineWhenPreviousClauseSpansMultipleLines); return false; } // The rest of the checks are only applied when the clause is not a query continuation clause. A continuation // clause is allowed to begin at the end of the previous clause, on the same line. if (clause.QueryClauseType != QueryClauseType.Continuation) { // The clause starts on the same line as the ending of the previous clause. // This is ok as long as we have not previously seen a clause which starts // on its own line. The one exception is that query continuation clauses // are allowed to be inserted at the end of the previous claus. if (clauseOnSeparateLine) { this.AddViolation(element, clause.LineNumber, Rules.QueryClausesMustBeOnSeparateLinesOrAllOnOneLine); return false; } // If the clause spans multiple lines, it must begin on its own line. The exception is query continuation // clauses, which are allowed to begin at the end of the previous claus. if (clause.Location.LineSpan > 1) { this.AddViolation(element, clause.LineNumber, Rules.QueryClausesSpanningMultipleLinesMustBeginOnOwnLine); return false; } // Indicate that we have seen a clause which starts on the same line as the // previous clause. clauseOnSameLine = true; } } else if (clause.LineNumber == previousClauseEndLineNumber + 1) { // The clause starts on the line just after the previous clause. // This is fine unless we have previously seen two clauses on the same line. if (clauseOnSameLine) { this.AddViolation(element, clause.LineNumber, Rules.QueryClausesMustBeOnSeparateLinesOrAllOnOneLine); return false; } // Indicate that we have seen a clause which begins on the line after // the previous clause. clauseOnSeparateLine = true; } else if (clause.LineNumber > previousClauseEndLineNumber + 1) { // The clause does not start on the line after the previous clause. this.AddViolation(element, clause.LineNumber, Rules.QueryClauseMustFollowPreviousClause); return false; } } previousClause = clause; if (clause.QueryClauseType == QueryClauseType.Continuation) { QueryContinuationClause continuationClause = (QueryContinuationClause)clause; if (!this.ProcessQueryClauses( element, expression, continuationClause, ref previousClause, ref clauseOnSameLine, ref clauseOnSeparateLine)) { return false; } } } return true; }
/// <summary> /// Checks the positioning of method parameters which are split across multiple lines. /// </summary> /// <param name="element">The element.</param> /// <param name="parameterList">The argument list.</param> /// <param name="arguments">The method arguments.</param> /// <param name="openingBracket">The opening bracket token.</param> private void CheckSplitMethodArgumentList(Element element, CodeUnit parameterList, IArgumentList arguments, OpenBracketToken openingBracket) { Param.AssertNotNull(element, "element"); Param.AssertNotNull(parameterList, "parameterList"); Param.AssertNotNull(arguments, "arguments"); Param.AssertNotNull(openingBracket, "openingBracket"); Token previousComma = null; bool commaOnSameLineAsPreviousParameterViolation = false; for (int i = 0; i < arguments.Count; ++i) { CodeLocation location = arguments.Location(i); int argumentStartLine = location.LineNumber; CodeUnit argument = arguments.Argument(i); // Some types of parameters or arguments are not allowed to span across multiple lines. if (location.LineSpan > 1 && !arguments.MaySpanMultipleLines(i)) { this.AddViolation(element, argumentStartLine, Rules.ParameterMustNotSpanMultipleLines); } if (i == 0) { // The first argument must start on the line after the opening bracket if (argumentStartLine != openingBracket.LineNumber + 1) { int commentLineSpan = MeasureCommentLinesAfter(openingBracket); if (argumentStartLine != openingBracket.LineNumber + commentLineSpan + 1) { this.AddViolation(element, argumentStartLine, Rules.SplitParametersMustStartOnLineAfterDeclaration, element.FriendlyTypeText); } } } else { // The argument must begin on the line after the previous comma. Debug.Assert(previousComma != null, "The previous comma should have been set."); if (!commaOnSameLineAsPreviousParameterViolation) { if (argumentStartLine != previousComma.LineNumber + 1) { int commentLineSpan = MeasureCommentLinesAfter(previousComma); if (argumentStartLine != previousComma.LineNumber + commentLineSpan + 1) { this.AddViolation(element, argumentStartLine, Rules.ParameterMustFollowComma); } } } } commaOnSameLineAsPreviousParameterViolation = false; // Find the comma after the token list. if (i < arguments.Count - 1) { Token lastTokenInArgument = argument.FindLastDescendentToken(); if (lastTokenInArgument != null) { for (Token token = lastTokenInArgument.FindNextDescendentTokenOf(parameterList); token != null; token = token.FindNextDescendentTokenOf(parameterList)) { if (token.TokenType == TokenType.Comma) { previousComma = token; // The comma must be on the same line as the previous argument. if (previousComma.LineNumber != location.EndPoint.LineNumber) { int commentLineSpan = MeasureCommentLinesBetween(argument.FindLastChildToken(), previousComma, false); if (previousComma.LineNumber != location.EndPoint.LineNumber + commentLineSpan) { this.AddViolation(element, token.LineNumber, Rules.CommaMustBeOnSameLineAsPreviousParameter); commaOnSameLineAsPreviousParameterViolation = true; } } break; } } } } } }
/// <summary> /// Checks the argument list to a method or method invocation to ensure that the arguments are /// positioned correctly. /// </summary> /// <param name="element">The element containing the expression.</param> /// <param name="parameterList">The element's argument list.</param> /// <param name="arguments">The arguments to the method.</param> /// <param name="openingBracket">The opening bracket token.</param> /// <param name="methodLineNumber">The line number on which the method begins.</param> private void CheckMethodArgumentList(Element element, CodeUnit parameterList, IArgumentList arguments, OpenBracketToken openingBracket, int methodLineNumber) { Param.AssertNotNull(element, "element"); Param.AssertNotNull(parameterList, "parameterList"); Param.AssertNotNull(arguments, "arguments"); Param.AssertNotNull(openingBracket, "openingBracket"); Param.AssertGreaterThanZero(methodLineNumber, "methodLineNumber"); // Determine whether all of the parameters are on the same line as one another. bool someParametersShareLine; bool someParameterOnDifferentLines; DetermineMethodParameterPlacementScheme( arguments, out someParametersShareLine, out someParameterOnDifferentLines); // All parameters must either be on the same line, or each argument must begin on its own line. if (someParametersShareLine && someParameterOnDifferentLines) { this.AddViolation( element, methodLineNumber, Rules.ParametersMustBeOnSameLineOrSeparateLines, element.FriendlyTypeText); } // Determine whether all of the parameters are on the same line as one another. if (someParameterOnDifferentLines) { this.CheckSplitMethodArgumentList(element, parameterList, arguments, openingBracket); } else if (arguments.Count > 0) { // The first argument must start on the same line as the opening bracket, or // on the line after it. int firstArgumentStartLine = arguments.Location(0).LineNumber; if (firstArgumentStartLine != openingBracket.LineNumber && firstArgumentStartLine != openingBracket.LineNumber + 1) { int commentLineSpan = MeasureCommentLinesAfter(openingBracket); if (firstArgumentStartLine != openingBracket.LineNumber + commentLineSpan + 1) { this.AddViolation(element, firstArgumentStartLine, Rules.ParameterListMustFollowDeclaration); } } } }
/// <summary> /// Checks a method or method invocation to ensure that the closing bracket is /// on the same line as the last argument. /// </summary> /// <param name="element">The element containing the expression.</param> /// <param name="parameterList">The argument list.</param> /// <param name="openingBracket">The opening bracket.</param> /// <param name="closingBracketType">The type of the closing bracket.</param> /// <param name="arguments">The arguments to the method.</param> private void CheckMethodClosingBracket( Element element, CodeUnit parameterList, OpenBracketToken openingBracket, TokenType closingBracketType, IArgumentList arguments) { Param.AssertNotNull(element, "element"); Param.AssertNotNull(parameterList, "parameterList"); Param.AssertNotNull(openingBracket, "openingBracket"); Param.Ignore(closingBracketType); Param.AssertNotNull(arguments, "arguments"); // Find the closing bracket. CloseBracketToken closingBracket = null; Token next = parameterList.FindNextSiblingToken(); if (next != null && next.Is(closingBracketType)) { closingBracket = (CloseBracketToken)next; } if (closingBracket != null) { if (arguments.Count == 0) { // The closing bracket must be on the same line as the opening bracket. if (openingBracket.LineNumber != closingBracket.LineNumber) { // If the brackets are not on the same line, determine if this is because there are comments // between the brackets. int commentLineSpan = MeasureCommentLinesBetween(openingBracket, closingBracket, false); if (openingBracket.LineNumber + commentLineSpan != closingBracket.LineNumber) { this.AddViolation(element, closingBracket.LineNumber, Rules.ClosingParenthesisMustBeOnLineOfOpeningParenthesis); } } } else { // The closing bracket must be on the same line as the end of the last method argument. int lastArgumentEndLine = arguments.Location(arguments.Count - 1).EndPoint.LineNumber; if (lastArgumentEndLine != closingBracket.LineNumber) { int commentLineSpan = MeasureCommentLinesBetween(arguments.Argument(arguments.Count - 1).FindLastDescendentToken(), closingBracket, false); if (lastArgumentEndLine + commentLineSpan != closingBracket.LineNumber) { this.AddViolation(element, closingBracket.LineNumber, Rules.ClosingParenthesisMustBeOnLineOfLastParameter); } } } } }
/// <summary> /// Checks a method or method invocation to ensure that the opening bracket is /// on the same line as the method declaration. /// </summary> /// <param name="element">The element containing the expression.</param> /// <param name="parameterList">The argument list.</param> /// <param name="openingBracketType">The type of the bracket that opens the argument list.</param> /// <returns>Returns the opening bracket.</returns> private OpenBracketToken CheckMethodOpeningBracket(Element element, CodeUnit parameterList, TokenType openingBracketType) { Param.AssertNotNull(element, "element"); Param.AssertNotNull(parameterList, "parameterList"); Param.Ignore(openingBracketType); // Find the opening bracket. OpenBracketToken openingBracket = null; Token firstDeclarationToken = element.FirstDeclarationToken; if (firstDeclarationToken != null) { Token previous = parameterList.FindPreviousSiblingToken(); if (previous != null && previous.Is(openingBracketType)) { openingBracket = (OpenBracketToken)previous; } if (openingBracket != null) { // Find the last word before the opening bracket. Token lastWord = openingBracket.FindPreviousToken(); if (lastWord != null && openingBracket.LineNumber != lastWord.LineNumber) { this.AddViolation( element, openingBracket.LineNumber, Rules.OpeningParenthesisMustBeOnDeclarationLine, element.FriendlyTypeText); } } } return openingBracket; }
/// <summary> /// Checks the placement and formatting of parameters to a method invocation or a method declaration. /// </summary> /// <param name="element">The element.</param> /// <param name="parameterList">The argument list.</param> /// <param name="methodArguments">The arguments or parameters to the method.</param> /// <param name="methodStartLineNumber">The line number on which the method begins.</param> /// <param name="openBracketType">The type of the argument list opening bracket.</param> /// <param name="closeBracketType">The type of the argument list closing bracket.</param> private void CheckParameters( Element element, CodeUnit parameterList, IArgumentList methodArguments, int methodStartLineNumber, TokenType openBracketType, TokenType closeBracketType) { Param.AssertNotNull(element, "element"); Param.AssertNotNull(parameterList, "parameterList"); Param.AssertNotNull(methodArguments, "methodArguments"); Param.AssertGreaterThanZero(methodStartLineNumber, "methodStartLineNumber"); Param.Ignore(openBracketType); Param.Ignore(closeBracketType); OpenBracketToken openingBracket = this.CheckMethodOpeningBracket(element, parameterList, openBracketType); if (openingBracket != null) { this.CheckMethodClosingBracket(element, parameterList, openingBracket, closeBracketType, methodArguments); if (methodArguments.Count > 0) { this.CheckMethodArgumentList(element, parameterList, methodArguments, openingBracket, methodStartLineNumber); } } }
/// <summary> /// Measures the number of lines taken up by comments after the start item before the first word. /// </summary> /// <param name="start">The start item.</param> /// <returns>Returns the number of lines takes up by comments.</returns> private static int MeasureCommentLinesAfter(CodeUnit start) { Param.AssertNotNull(start, "start"); int lineSpan = 0; int previousLineSpan = -1; int previousEndLineNumber = -1; for (CodeUnit next = start.FindNext(); next != null; next = next.FindNext()) { if (next.Is(LexicalElementType.Comment) || next.Is(CodeUnitType.Attribute)) { int itemLineSpan = ParameterPrewordOffset(next); if (previousEndLineNumber > 0 && next.LineNumber == previousEndLineNumber && previousLineSpan > 0) { --itemLineSpan; } lineSpan += itemLineSpan; previousLineSpan = itemLineSpan; previousEndLineNumber = next.Location.EndPoint.LineNumber; next = next.FindLast(); } else if (!next.Is(LexicalElementType.WhiteSpace) && !next.Is(LexicalElementType.EndOfLine) && !next.Is(CodeUnitType.ParameterList) && !next.Is(CodeUnitType.ArgumentList) && !next.Is(CodeUnitType.Parameter) && !next.Is(CodeUnitType.Argument)) { break; } } return lineSpan; }
/// <summary> /// Measures the number of lines taken up by comments between two tokens. /// </summary> /// <param name="start">The start token.</param> /// <param name="end">The end token.</param> /// <param name="includeAttributes">Indicates whether to also count attributes.</param> /// <returns>Returns the number of lines takes up by comments.</returns> private static int MeasureCommentLinesBetween(CodeUnit start, CodeUnit end, bool includeAttributes) { Param.AssertNotNull(start, "start"); Param.AssertNotNull(end, "end"); Param.Ignore(includeAttributes); int lineSpan = 0; int previousLineSpan = -1; int previousEndLineNumber = -1; for (CodeUnit next = start.FindNext(); next != null && next != end; next = next.FindNext()) { if (next.Is(LexicalElementType.Comment) || (next.Is(CodeUnitType.Attribute) && includeAttributes)) { int itemLineSpan = ParameterPrewordOffset(next); if (previousEndLineNumber > 0 && next.LineNumber == previousEndLineNumber && previousLineSpan > 0) { --itemLineSpan; } lineSpan += itemLineSpan; previousLineSpan = itemLineSpan; previousEndLineNumber = next.Location.EndPoint.LineNumber; } } return lineSpan; }
/// <summary> /// Visits one code unit in the document. /// </summary> /// <param name="codeUnit">The item being visited.</param> /// <param name="parentElement">The parent element, if any.</param> /// <param name="parentStatement">The parent statement, if any.</param> /// <param name="parentExpression">The parent expression, if any.</param> /// <param name="parentClause">The parent query clause, if any.</param> /// <param name="parentToken">The parent token, if any.</param> /// <param name="topLevelElements">The number of classes and namespaces seen in the document.</param> /// <returns>Returns true to continue, or false to stop the walker.</returns> private bool VisitCodeUnit( CodeUnit codeUnit, Element parentElement, Statement parentStatement, Expression parentExpression, QueryClause parentClause, Token parentToken, TopLevelElements topLevelElements) { Param.AssertNotNull(codeUnit, "codeUnit"); Param.Ignore(parentElement, parentStatement, parentExpression, parentClause, parentToken); Param.AssertNotNull(topLevelElements, "topLevelElements"); if (codeUnit.CodeUnitType == CodeUnitType.Element) { return this.VisitElement((Element)codeUnit, parentElement, topLevelElements); } else if (codeUnit.CodeUnitType == CodeUnitType.Statement) { return this.VisitStatement((Statement)codeUnit, parentExpression, parentStatement, parentElement); } else if (codeUnit.CodeUnitType == CodeUnitType.Expression) { return this.VisitExpression((Expression)codeUnit, parentExpression, parentStatement, parentElement); } return !this.Cancel; }
internal static void Violate(this SourceAnalyzer sourceAnalyzer, CsElement element, CodeUnit codeUnit, params object[] args) { Violate(sourceAnalyzer, element, codeUnit.Location, args); }
/// <summary> /// Records information about the given item, under the given node. /// </summary> /// <param name="item">The item to record.</param> /// <param name="parentNode">The Xml node to record this item beneath.</param> /// <returns>Returns the new Xml node describing this item.</returns> private static XmlNode RecordItem(CodeUnit item, XmlNode parentNode) { Param.AssertNotNull(item, "item"); Param.AssertNotNull(parentNode, "parentNode"); // Create a new node for this item and add it to the parent. XmlNode codeUnitNode = parentNode.OwnerDocument.CreateElement("CodeUnit"); parentNode.AppendChild(codeUnitNode); if (item.Is(CodeUnitType.LexicalElement)) { XmlAttribute text = parentNode.OwnerDocument.CreateAttribute("Text"); text.Value = ((LexicalElement)item).Text; codeUnitNode.Attributes.Append(text); } XmlAttribute type = parentNode.OwnerDocument.CreateAttribute("Type"); type.Value = item.GetType().Name; codeUnitNode.Attributes.Append(type); return codeUnitNode; }