/// <summary> /// Finds the given class member in the given class. /// </summary> /// <param name="word">The word to check.</param> /// <param name="parentClass">The class the word appears in.</param> /// <param name="members">The collection of members of the parent class.</param> /// <param name="interfaces">True if interface implementations should be included.</param> /// <returns>Returns the class members that match against the given name.</returns> private static ICollection<Element> FindClassMember( string word, ClassBase parentClass, Dictionary<string, List<Element>> members, bool interfaces) { Param.AssertNotNull(word, "word"); Param.AssertNotNull(parentClass, "parentClass"); Param.AssertNotNull(members, "members"); Param.Ignore(interfaces); // If the word is the same as the class name, then this is a constructor and we // don't want to match against it. if (word != parentClass.Name) { ICollection<Element> matches = ReadabilityRules.MatchClassMember(word, members, interfaces); if (matches != null && matches.Count > 0) { return matches; } } return null; }
/// <summary> /// Checks a word to see if it should start with this. or base. /// </summary> /// <param name="word">The word text to check.</param> /// <param name="item">The word being checked.</param> /// <param name="line">The line that the word appears on.</param> /// <param name="expression">The expression the word appears within.</param> /// <param name="parentElement">The element that contains the word.</param> /// <param name="parentClass">The parent class that this element belongs to.</param> /// <param name="members">The collection of members of the parent class.</param> private void CheckWordUsageAgainstClassMemberRules( string word, Token item, int line, Expression expression, Element parentElement, ClassBase parentClass, Dictionary<string, List<Element>> members) { Param.AssertValidString(word, "word"); Param.AssertNotNull(item, "item"); Param.AssertGreaterThanZero(line, "line"); Param.AssertNotNull(expression, "expression"); Param.AssertNotNull(parentElement, "parentElement"); Param.Ignore(parentClass); Param.Ignore(members); // If there is a local variable with the same name, or if the item we're checking is within the left-hand side // of an object initializer expression, then ignore it. if (!IsLocalMember(word, item, expression) && !IsObjectInitializerLeftHandSideExpression(expression)) { // Determine if this is a member of our class. Element foundMember = null; ICollection<Element> classMembers = ReadabilityRules.FindClassMember(word, parentClass, members, false); if (classMembers != null) { foreach (Element classMember in classMembers) { if (classMember.ContainsModifier(TokenType.Static) || (classMember.ElementType == ElementType.Field && ((Field)classMember).Const)) { // There is a member with a matching name that is static or is a const field. In this case, // ignore the issue and quit. foundMember = null; break; } else if (classMember.ElementType != ElementType.Class && classMember.ElementType != ElementType.Struct && classMember.ElementType != ElementType.Delegate && classMember.ElementType != ElementType.Enum) { // Found a matching member. if (foundMember == null) { foundMember = classMember; } } } if (foundMember != null) { if (foundMember.ElementType == ElementType.Property) { // If the property's name and type are the same, then this is not a violation. // In this case, the type is being accessed, not the property. Property property = (Property)foundMember; if (property.ReturnType.Text != property.Name) { this.Violation<Token>( Rules.PrefixLocalCallsWithThis, new ViolationContext(parentElement, line, word), this.FixPrefixLocalCallsWithThisViolation, item); } } else { this.Violation<Token>( Rules.PrefixLocalCallsWithThis, new ViolationContext(parentElement, line, word), this.FixPrefixLocalCallsWithThisViolation, item); } } } } }
/// <summary> /// Parses the given literal token. /// </summary> /// <param name="token">The literal token node.</param> /// <param name="expression">The expression that contains the token.</param> /// <param name="parentExpression">The parent of the expression that contains the token.</param> /// <param name="parentElement">The element that contains the expression.</param> /// <param name="parentClass">The class that the element belongs to.</param> /// <param name="members">The collection of members of the parent class.</param> private void CheckClassMemberRulesForLiteralToken( Token token, Expression expression, Expression parentExpression, Element parentElement, ClassBase parentClass, Dictionary<string, List<Element>> members) { Param.AssertNotNull(token, "token"); Param.AssertNotNull(expression, "expression"); Param.Ignore(parentExpression); Param.AssertNotNull(parentElement, "parentElement"); Param.Ignore(parentClass); Param.Ignore(members); // Skip types. We only care about named members. if (token.TokenType != TokenType.Type) { // If the name starts with a dot, ignore it. if (!token.Text.StartsWith(".", StringComparison.Ordinal)) { if (token.Text == "base" && parentExpression != null) { // An item is only allowed to start with base if there is an implementation of the // item in the local class and the caller is trying to explicitly call the base // class implementation instead of the local class implementation. Extract the name // of the item being accessed. Token name = ReadabilityRules.ExtractBaseClassMemberName(parentExpression, token); if (name != null) { ICollection<Element> matches = ReadabilityRules.FindClassMember(name.Text, parentClass, members, true); // Check to see if there is a non-static match. bool found = false; if (matches != null) { foreach (Element match in matches) { if (!match.ContainsModifier(TokenType.Static)) { found = true; break; } } } if (!found) { this.AddViolation(parentElement, name.LineNumber, Rules.DoNotPrefixCallsWithBaseUnlessLocalImplementationExists, name); } } } else if (token.Text != "this") { // Check whether this word should really start with this. this.CheckWordUsageAgainstClassMemberRules( token.Text, token, token.LineNumber, expression, parentElement, parentClass, members); } } } }
/// <summary> /// Parses the given query clause. /// </summary> /// <param name="clause">The query clause.</param> /// <param name="parentExpression">The parent expression, if there is one.</param> /// <param name="parentElement">The element that contains the expressions.</param> /// <param name="parentClass">The class that the element belongs to.</param> /// <param name="members">The collection of members of the parent class.</param> private void CheckClassMemberRulesForQueryClause( QueryClause clause, Expression parentExpression, Element parentElement, ClassBase parentClass, Dictionary<string, List<Element>> members) { Param.AssertNotNull(clause, "clause"); Param.Ignore(parentExpression); Param.AssertNotNull(parentElement, "parentElement"); Param.AssertNotNull(parentClass, "parentClass"); Param.AssertNotNull(members, "members"); for (CodeUnit child = clause.FindFirstChild(); child != null; child = child.FindNextSibling()) { if (child.Is(CodeUnitType.QueryClause)) { this.CheckClassMemberRulesForQueryClause((QueryClause)child, parentExpression, parentElement, parentClass, members); } else if (child.Is(CodeUnitType.Expression)) { this.CheckClassMemberRulesForExpression((Expression)child, parentExpression, parentElement, parentClass, members); } else if (child.Is(TokenType.Literal)) { Token token = (Token)child; // Check to see whether this literal is preceded by a member access symbol. If not // then we want to check whether this is a reference to one of our class members. if (!IsLiteralTokenPrecededByMemberAccessSymbol(token)) { // Process the literal. this.CheckClassMemberRulesForLiteralToken( token, parentExpression, parentExpression, parentElement, parentClass, members); } } } }
/// <summary> /// Parses the given expression. /// </summary> /// <param name="expression">The expression.</param> /// <param name="parentExpression">The parent expression, if there is one.</param> /// <param name="parentElement">The element that contains the expressions.</param> /// <param name="parentClass">The class that the element belongs to.</param> /// <param name="members">The collection of members of the parent class.</param> private void CheckClassMemberRulesForExpression( Expression expression, Expression parentExpression, Element parentElement, ClassBase parentClass, Dictionary<string, List<Element>> members) { Param.AssertNotNull(expression, "expression"); Param.Ignore(parentExpression); Param.AssertNotNull(parentElement, "parentElement"); Param.AssertNotNull(parentClass, "parentClass"); Param.AssertNotNull(members, "members"); if (expression.ExpressionType == ExpressionType.Literal) { LiteralExpression literalExpression = (LiteralExpression)expression; // Check to see whether this literal is preceded by a member access symbol. If not // then we want to check whether this is a reference to one of our class members. if (!IsLiteralTokenPrecededByMemberAccessSymbol(literalExpression.Token)) { // Process the literal. this.CheckClassMemberRulesForLiteralToken( literalExpression.Token, expression, parentExpression, parentElement, parentClass, members); } } else { if (expression.ExpressionType == ExpressionType.Assignment && parentExpression != null && parentExpression.ExpressionType == ExpressionType.CollectionInitializer) { // When we encounter assignment expressions within collection initializer expressions, we ignore the expression // on the left-hand side of the assignment. This is because we know that the left-hand side refers to a property on // the type being initialized, not a property on the local class. Thus, it does not ever need to be prefixed by this. // Without this check we can get name collisions, such as: // public sealed class Person // { // public string FirstName { get; } // public void CreateAnonymousType() // { // var anonymousType = new { FirstName = this.FirstName }; // } // } this.CheckClassMemberRulesForExpression(((AssignmentExpression)expression).RightHandSide, expression, parentElement, parentClass, members); } else if (expression.Children.ExpressionCount > 0) { // Check each child expression within this expression. this.CheckClassMemberRulesForExpressions(expression, expression, parentElement, parentClass, members); } if (expression.Children.QueryClauseCount > 0) { for (QueryClause clause = expression.FindFirstChildQueryClause(); clause != null; clause = clause.FindNextSiblingQueryClause()) { this.CheckClassMemberRulesForQueryClause(clause, expression, parentElement, parentClass, members); } } // Check if this is an anonymous method expression, which contains a child statement list. if (expression.ExpressionType == ExpressionType.AnonymousMethod) { // Check the statements under this anonymous method. this.CheckClassMemberRulesForStatements(expression, parentElement, parentClass, members); } else if (expression.ExpressionType == ExpressionType.MethodInvocation) { // Check each of the arguments passed into the method call. MethodInvocationExpression methodInvocation = (MethodInvocationExpression)expression; foreach (Argument argument in methodInvocation.ArgumentList) { // Check each expression within this child expression. this.CheckClassMemberRulesForExpression( argument.Expression, null, parentElement, parentClass, members); } } } }
/// <summary> /// Parses the list of expressions. /// </summary> /// <param name="expressionParent">The direct parent of the expressions to check.</param> /// <param name="parentExpression">The parent expression, if there is one.</param> /// <param name="parentElement">The element that contains the expressions.</param> /// <param name="parentClass">The class that the element belongs to.</param> /// <param name="members">The collection of members of the parent class.</param> private void CheckClassMemberRulesForExpressions( CodeUnit expressionParent, Expression parentExpression, Element parentElement, ClassBase parentClass, Dictionary<string, List<Element>> members) { Param.AssertNotNull(expressionParent, "expressionParent"); Param.AssertNotNull(parentElement, "parentElement"); Param.Ignore(parentExpression); Param.Ignore(parentClass); Param.Ignore(members); // Loop through each of the expressions in the list. for (Expression expression = expressionParent.FindFirstChildExpression(); expression != null; expression = expression.FindNextSiblingExpression()) { // If the expression is a variable declarator expression, we don't // want to match against the identifier tokens. if (expression.ExpressionType == ExpressionType.VariableDeclarator) { VariableDeclaratorExpression declarator = (VariableDeclaratorExpression)expression; if (declarator.Initializer != null) { this.CheckClassMemberRulesForExpression(declarator.Initializer, parentExpression, parentElement, parentClass, members); } } else { this.CheckClassMemberRulesForExpression(expression, parentExpression, parentElement, parentClass, members); } } }
/// <summary> /// Parses the given statement list. /// </summary> /// <param name="statementParent">The direct parent of the statements to check.</param> /// <param name="parentElement">The element that contains the statements.</param> /// <param name="parentClass">The class that the element belongs to.</param> /// <param name="members">The collection of members of the parent class.</param> private void CheckClassMemberRulesForStatements( CodeUnit statementParent, Element parentElement, ClassBase parentClass, Dictionary<string, List<Element>> members) { Param.AssertNotNull(statementParent, "statementParent"); Param.AssertNotNull(parentElement, "parentElement"); Param.Ignore(parentClass); Param.Ignore(members); // Loop through each of the statements. for (Statement statement = statementParent.FindFirstChildStatement(); statement != null; statement = statement.FindNextSiblingStatement()) { if (statement.Children.StatementCount > 0) { // Parse the sub-statements. this.CheckClassMemberRulesForStatements(statement, parentElement, parentClass, members); } // Parse the expressions in the statement. this.CheckClassMemberRulesForExpressions(statement, null, parentElement, parentClass, members); } }
/// <summary> /// Checks the items within the given element. /// </summary> /// <param name="element">The element to check.</param> /// <param name="parentClass">The class that the element belongs to.</param> /// <param name="members">The collection of members of the parent class.</param> /// <returns>Returns false if the analyzer should quit.</returns> private bool CheckClassMemberRulesForElements(Element element, ClassBase parentClass, Dictionary<string, List<Element>> members) { Param.AssertNotNull(element, "element"); Param.Ignore(parentClass); Param.Ignore(members); // Check whether processing has been cancelled by the user. if (this.Cancel) { return false; } if (element.ElementType == ElementType.Class || element.ElementType == ElementType.Struct || element.ElementType == ElementType.Interface) { parentClass = element as ClassBase; members = CollectClassMembers(parentClass); } for (Element child = element.FindFirstChildElement(); child != null; child = child.FindNextSiblingElement()) { if (!child.Generated) { if (child.ElementType == ElementType.Method || child.ElementType == ElementType.Constructor || child.ElementType == ElementType.Destructor || child.ElementType == ElementType.Accessor) { // If the parent class is null, then this element is sitting outside of a class. // This is illegal in C# so the code will not compile, but we still attempt to // parse it. In this case there is no use of this prefixes since there is no class. if (parentClass != null) { this.CheckClassMemberRulesForStatements(child, child, parentClass, members); } } else { if (child.ElementType == ElementType.Class || child.ElementType == ElementType.Struct) { ClassBase elementContainer = child as ClassBase; Debug.Assert(elementContainer != null, "The element is not a class."); this.CheckClassMemberRulesForElements(child, elementContainer, members); } else if (!this.CheckClassMemberRulesForElements(child, parentClass, members)) { return false; } } } } return true; }
/// <summary> /// Adds all members of a class to a dictionary. /// </summary> /// <param name="class">The class to collect.</param> /// <param name="members">Adds all members of the class to the given dictionary.</param> private static void CollectClassMembersAux(ClassBase @class, Dictionary<string, List<Element>> members) { Param.AssertNotNull(@class, "class"); Param.AssertNotNull(members, "members"); for (Element child = @class.FindFirstChildElement(); child != null; child = child.FindNextSiblingElement()) { if (child.ElementType == ElementType.Field) { // Look through each of the declarators in the field. var variableDeclaration = ((Field)child).VariableDeclarationStatement; if (variableDeclaration != null) { foreach (var declarator in variableDeclaration.Declarators) { AddClassMember(members, child, declarator.Identifier.Text); } } } else if (child.ElementType == ElementType.Event) { // Look through each of the declarators in the event. foreach (EventDeclaratorExpression declarator in ((Event)child).Declarators) { AddClassMember(members, child, declarator.Identifier.Text); } } else if (child.ElementType != ElementType.EmptyElement) { AddClassMember(members, child, child.Name); } } }
/// <summary> /// Adds all members of a class to a dictionary, taking into account partial classes. /// </summary> /// <param name="parentClass">The class to collect.</param> /// <returns>Returns the dictionary of class members.</returns> private static Dictionary<string, List<Element>> CollectClassMembers(ClassBase parentClass) { Param.AssertNotNull(parentClass, "parentClass"); Dictionary<string, List<Element>> members = new Dictionary<string, List<Element>>(); if (parentClass.ContainsModifier(TokenType.Partial)) { foreach (ClassBase @class in parentClass.PartialElementCollection) { CollectClassMembersAux(@class, members); } } else { CollectClassMembersAux(parentClass, members); } return members; }