/// <summary> /// Checks the Xml header block of the given class for consistency with the class. /// </summary> /// <param name="classElement">The element to parse.</param> /// <param name="settings">The analyzer settings.</param> private void CheckClassElementHeader(ClassBase classElement, AnalyzerSettings settings) { Param.AssertNotNull(classElement, "classElement"); Param.Ignore(settings); AnalyzerSettings adjustedSettings = settings; adjustedSettings.RequireFields = false; if (classElement.Declaration.ContainsModifier(CsTokenType.Partial)) { // This is a partial element. Check for the possible partial element header types. this.CheckHeader(classElement, adjustedSettings, true); } else { // Just perform the regular set of checks on the header. this.CheckHeader(classElement, adjustedSettings, false); } }
/* /// <summary> /// Determines whether to reference the set accessor within the property's summary documentation. /// </summary> /// <param name="property">The property.</param> /// <param name="setAccessor">The set accessor.</param> /// <returns>Returns true to reference the set accessor in the summary documentation, or false to omit it.</returns> private static bool IncludeSetAccessorInDocumentation(Property property, Accessor setAccessor) { Param.AssertNotNull(property, "property"); Param.AssertNotNull(setAccessor, "setAccessor"); // If the set accessor has the same access modifier as the property, always include it in the documentation. // Accessors get 'private' access modifiers by default if no access modifier is defined, in which case they // default to having the access of their parent property. Also include documentation for the set accessor // if it appears to be private but it does not actually define the 'private' keyword. if (setAccessor.AccessModifier == property.AccessModifier || (setAccessor.AccessModifier == AccessModifierType.Private && !setAccessor.Declaration.ContainsModifier(CsTokenType.Private))) { return true; } // If the property is visible externally, then only include the set accessor in the documentation if // it is marked as public or protected. if (property.ActualAccess == AccessModifierType.Public || property.ActualAccess == AccessModifierType.Protected || property.ActualAccess == AccessModifierType.ProtectedInternal) { if (setAccessor.AccessModifier == AccessModifierType.Public || setAccessor.AccessModifier == AccessModifierType.Protected || setAccessor.AccessModifier == AccessModifierType.ProtectedInternal) { return true; } } // If the property is internal, the include the set accessor in the docs unless it contains the private keyword. if (property.ActualAccess == AccessModifierType.Internal || property.ActualAccess == AccessModifierType.ProtectedAndInternal) { if (!setAccessor.Declaration.ContainsModifier(CsTokenType.Private)) { return true; } } // If the property is private, always include the set accessor in the docs. if (property.ActualAccess == AccessModifierType.Private) { return true; } // Otherwise, do not include the set accessor in the docs. return false; } */ /// <summary> /// Builds a regular expression that can be used to validate the name of the given type when /// used within a documentation cref attribute. /// </summary> /// <param name="type">The type to match against.</param> /// <returns>Returns the regular expression object.</returns> private static string BuildCrefValidationStringForType(ClassBase type) { Param.AssertNotNull(type, "type"); // Determine whether the type is generic. string typeName = type.Declaration.Name; string[] genericParams = null; string genericParametersRegex = null; int index = typeName.IndexOf('<'); if (index > 0) { // Get the generic types from the type name. genericParams = ExtractGenericParametersFromType(typeName, index); if (genericParams != null && genericParams.Length > 0) { genericParametersRegex = BuildGenericParametersRegex(genericParams); } // Remove the generic brackets from the type name. typeName = typeName.Substring(0, index); } // Also capture the fully qualified name of the type, without generic parameters included. string namespaceRegex = BuildNamespaceRegex(type); // Build the regex string to match all possible formats for the type name. return string.Format( CultureInfo.InvariantCulture, CrefRegex, namespaceRegex, typeName, genericParametersRegex == null ? string.Empty : genericParametersRegex, genericParams == null ? string.Empty : "`" + genericParams.Length.ToString(CultureInfo.InvariantCulture)); }
/// <summary> /// Gets a string to match the namespace of the type in a format that can be inserted into a regular expression for matching. /// </summary> /// <param name="type">The type to match.</param> /// <returns>Returns the namespace string.</returns> private static string BuildNamespaceRegex(ClassBase type) { Param.AssertNotNull(type, "type"); // The fully-qualified name of a type should always begin with Root. This part should be ignored. string fullyQualifiedName = type.FullyQualifiedName; Debug.Assert(fullyQualifiedName.StartsWith("Root.", StringComparison.Ordinal), "The fully qualified name of a type should start with Root."); StringBuilder namespaceRegex = new StringBuilder(); int start = 5; // Start at position 5 to skip past the 'Root.' prefix. for (int i = start; i < fullyQualifiedName.Length; ++i) { if (fullyQualifiedName[i] == '.') { // Since a dot is a special character in regex syntax, we need to escape this character. if (namespaceRegex.Length > 0) { namespaceRegex.Append("\\."); } namespaceRegex.Append(fullyQualifiedName.Substring(start, i - start)); start = i + 1; } else if (fullyQualifiedName[i] == '<') { // Stop if we get to an opening generic bracket. break; } } if (namespaceRegex.Length > 0) { namespaceRegex.Append("\\."); } return namespaceRegex.ToString(); }
/// <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, CsToken item, int line, Expression expression, CsElement parentElement, ClassBase parentClass, Dictionary<string, List<CsElement>> 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. CsElement foundMember = null; ICollection<CsElement> classMembers = ReadabilityRules.FindClassMember(word, parentClass, members, false); if (classMembers != null) { if (classMembers != null) { foreach (CsElement classMember in classMembers) { if (classMember.Declaration.ContainsModifier(CsTokenType.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.Declaration.Name) { this.AddViolation(parentElement, line, Rules.PrefixLocalCallsWithThis, word); } } else { this.AddViolation(parentElement, line, Rules.PrefixLocalCallsWithThis, word); } } } } }
/// <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<CsElement>> CollectClassMembers(ClassBase parentClass) { Param.AssertNotNull(parentClass, "parentClass"); Dictionary<string, List<CsElement>> members = new Dictionary<string, List<CsElement>>(); if (parentClass.Declaration.ContainsModifier(CsTokenType.Partial)) { foreach (ClassBase @class in parentClass.PartialElementList) { CollectClassMembersAux(@class, members); } } else { CollectClassMembersAux(parentClass, members); } return members; }
////private void CheckClassMemberRulesForChildExpressions( //// Expression expression, //// Expression parentExpression, //// CsElement parentElement, //// ClassBase parentClass, //// Dictionary<string, List<CsElement>> members) ////{ //// Param.AssertNotNull(expression, "expression"); //// Param.Ignore(parentExpression); //// Param.AssertNotNull(parentElement, "parentElement"); //// Param.AssertNotNull(parentClass, "parentClass"); //// Param.AssertNotNull(members, "members"); //// foreach (Expression childExpression in expression.ChildExpressions) //// { //// ExpressionWithParameters expressionWithParameters = childExpression as ExpressionWithParameters; //// if (expressionWithParameters != null) //// { //// foreach (Parameter parameter in expressionWithParameters.Parameters) //// { //// if (parameter.Type == null && parameter.Name != null) //// { //// //todo //// } //// } //// } //// else if (childExpression.ExpressionType == ExpressionType.MethodInvocation) //// { //// MethodInvocationExpression methodInvocation = childExpression as MethodInvocationExpression; //// foreach (Expression argument in methodInvocation.Arguments) //// { //// // Check each expression within this child expression. //// this.CheckClassMemberRulesForExpressions( //// argument.ChildExpressions, //// argument, //// parentElement, //// parentClass, //// members); //// } //// } //// this.CheckClassMemberRulesForChildExpressions(childExpression, expression, parentElement, parentClass, members); //// } ////} /// <summary> /// Parses the given literal token. /// </summary> /// <param name="tokenNode">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( Node<CsToken> tokenNode, Expression expression, Expression parentExpression, CsElement parentElement, ClassBase parentClass, Dictionary<string, List<CsElement>> members) { Param.AssertNotNull(tokenNode, "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 (!(tokenNode.Value is TypeToken)) { // If the name starts with a dot, ignore it. if (!tokenNode.Value.Text.StartsWith(".", StringComparison.Ordinal)) { if (tokenNode.Value.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. CsToken name = ReadabilityRules.ExtractBaseClassMemberName(parentExpression, tokenNode); if (name != null) { ICollection<CsElement> 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 (CsElement match in matches) { if (!match.Declaration.ContainsModifier(CsTokenType.Static)) { found = true; break; } } } if (!found) { this.AddViolation(parentElement, name.LineNumber, Rules.DoNotPrefixCallsWithBaseUnlessLocalImplementationExists, name); } } } else if (tokenNode.Value.Text != "this") { // Check whether this word should really start with this. this.CheckWordUsageAgainstClassMemberRules( tokenNode.Value.Text, tokenNode.Value, tokenNode.Value.LineNumber, expression, parentElement, parentClass, members); } } } }
/// <summary> /// Parses the given statement list. /// </summary> /// <param name="statements">The list of statements to parse.</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( ICollection<Statement> statements, CsElement parentElement, ClassBase parentClass, Dictionary<string, List<CsElement>> members) { Param.AssertNotNull(statements, "statements"); Param.AssertNotNull(parentElement, "parentElement"); Param.Ignore(parentClass); Param.Ignore(members); // Loop through each of the statements. foreach (Statement statement in statements) { if (statement.ChildStatements.Count > 0) { // Parse the sub-statements. this.CheckClassMemberRulesForStatements(statement.ChildStatements, parentElement, parentClass, members); } // Parse the expressions in the statement. this.CheckClassMemberRulesForExpressions(statement.ChildExpressions, null, 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, CsElement parentElement, ClassBase parentClass, Dictionary<string, List<CsElement>> 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.TokenNode, expression.Tokens.MasterList)) { // Process the literal. this.CheckClassMemberRulesForLiteralToken( literalExpression.TokenNode, 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.ChildExpressions.Count > 0) { // Check each child expression within this expression. this.CheckClassMemberRulesForExpressions( expression.ChildExpressions, 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.ChildStatements, 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.Arguments) { // Check each expression within this child expression. this.CheckClassMemberRulesForExpression( argument.Expression, null, parentElement, parentClass, members); } } ////else if (expression.ExpressionType == ExpressionType.MemberAccess) ////{ //// MemberAccessExpression memberAccess = (MemberAccessExpression)expression; //// if (memberAccess.OperatorType != MemberAccessExpression.Operator.QualifiedAlias) //// { //// this.CheckClassMemberRulesForLiteralToken( //// memberAccess.Tokens.First, //// expression, //// parentElement, //// parentClass, //// members); //// } ////} } }
/// <summary> /// Parses the list of expressions. /// </summary> /// <param name="expressions">The list of expressions.</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( ICollection<Expression> expressions, Expression parentExpression, CsElement parentElement, ClassBase parentClass, Dictionary<string, List<CsElement>> members) { Param.AssertNotNull(expressions, "expressions"); Param.AssertNotNull(parentElement, "parentElement"); Param.Ignore(parentExpression); Param.Ignore(parentClass); Param.Ignore(members); // Loop through each of the expressions in the list. foreach (Expression expression in expressions) { // 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 = expression as VariableDeclaratorExpression; if (declarator.Initializer != null) { this.CheckClassMemberRulesForExpression(declarator.Initializer, parentExpression, parentElement, parentClass, members); } } else { this.CheckClassMemberRulesForExpression(expression, parentExpression, parentElement, parentClass, members); } } }
/// <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<CsElement> FindClassMember( string word, ClassBase parentClass, Dictionary<string, List<CsElement>> 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.Declaration.Name) { ICollection<CsElement> matches = ReadabilityRules.MatchClassMember(word, members, interfaces); if (matches != null && matches.Count > 0) { return matches; } } return null; }
/// <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(CsElement element, ClassBase parentClass, Dictionary<string, List<CsElement>> 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); } foreach (CsElement child in element.ChildElements) { 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.ChildStatements, 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<CsElement>> members) { Param.AssertNotNull(@class, "class"); Param.AssertNotNull(members, "members"); foreach (CsElement child in @class.ChildElements) { if (child.ElementType == ElementType.Field) { // Look through each of the declarators in the field. foreach (VariableDeclaratorExpression declarator in ((Field)child).VariableDeclarationStatement.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.Declaration.Name); } } }
/// <summary> /// Gets the actual qualified namespace of the class. For nested types where A.B.C.D exists and C.D is the type it returns A.B rather than A.B.C. /// </summary> /// <param name="type">The type to get the namespace for.</param> /// <returns>A string of the actual namespace.</returns> private static string GetActualQualifiedNamespace(ClassBase type) { Param.AssertNotNull(type, "type"); CsElement localType = type; while (localType.Parent is ClassBase) { localType = (CsElement)localType.Parent; } string fullyQualifiedNameOfParentClass = localType.FullyQualifiedName; int lastIndexOfDot = fullyQualifiedNameOfParentClass.LastIndexOf('.'); return lastIndexOfDot == -1 ? string.Empty : fullyQualifiedNameOfParentClass.Substring(0, lastIndexOfDot + 1); }
/// <summary> /// Gets the actual name of the class. If nested type returns A+B rather than only B. /// </summary> /// <param name="type">The type to get the correct class name for.</param> /// <param name="showPlusInTypeName">True to show a '+' sign in a nested type.</param> /// <returns>A string of the actual class name.</returns> private static string GetActualClassName(ClassBase type, bool showPlusInTypeName) { Param.AssertNotNull(type, "type"); Param.Ignore(showPlusInTypeName); CsElement localType = type; while (localType.Parent is ClassBase) { localType = (CsElement)localType.Parent; } int lastIndexOfDot = localType.FullyQualifiedName.LastIndexOf('.'); if (lastIndexOfDot == -1) { return type.Name; } else { Debug.Assert(type.FullNamespaceName.Length > lastIndexOfDot + 1, "The fully qualified name is corrupted."); string remainder = type.FullyQualifiedName.Substring(lastIndexOfDot + 1); return showPlusInTypeName ? remainder.Replace('.', '+') : remainder; } }
/// <summary> /// Gets a string to match the namespace of the type in a format that can be inserted into a regular expression for matching. /// </summary> /// <param name="type">The type to match.</param> /// <returns>Returns the namespace string.</returns> private static string BuildNamespaceRegex(ClassBase type) { Param.AssertNotNull(type, "type"); // The fully-qualified name of a type should always begin with Root. This part should be ignored. string actualQualifiedClassName = GetActualQualifiedNamespace(type); Debug.Assert(actualQualifiedClassName.StartsWith("Root.", StringComparison.Ordinal), "The fully qualified name of a type should start with Root."); return actualQualifiedClassName.Substring(5).Replace(".", "\\."); }
/* /// <summary> /// Determines whether to reference the set accessor within the property's summary documentation. /// </summary> /// <param name="property">The property.</param> /// <param name="setAccessor">The set accessor.</param> /// <returns>Returns true to reference the set accessor in the summary documentation, or false to omit it.</returns> private static bool IncludeSetAccessorInDocumentation(Property property, Accessor setAccessor) { Param.AssertNotNull(property, "property"); Param.AssertNotNull(setAccessor, "setAccessor"); // If the set accessor has the same access modifier as the property, always include it in the documentation. // Accessors get 'private' access modifiers by default if no access modifier is defined, in which case they // default to having the access of their parent property. Also include documentation for the set accessor // if it appears to be private but it does not actually define the 'private' keyword. if (setAccessor.AccessModifier == property.AccessModifier || (setAccessor.AccessModifier == AccessModifierType.Private && !setAccessor.Declaration.ContainsModifier(CsTokenType.Private))) { return true; } // If the property is visible externally, then only include the set accessor in the documentation if // it is marked as public or protected. if (property.ActualAccess == AccessModifierType.Public || property.ActualAccess == AccessModifierType.Protected || property.ActualAccess == AccessModifierType.ProtectedInternal) { if (setAccessor.AccessModifier == AccessModifierType.Public || setAccessor.AccessModifier == AccessModifierType.Protected || setAccessor.AccessModifier == AccessModifierType.ProtectedInternal) { return true; } } // If the property is internal, the include the set accessor in the docs unless it contains the private keyword. if (property.ActualAccess == AccessModifierType.Internal || property.ActualAccess == AccessModifierType.ProtectedAndInternal) { if (!setAccessor.Declaration.ContainsModifier(CsTokenType.Private)) { return true; } } // If the property is private, always include the set accessor in the docs. if (property.ActualAccess == AccessModifierType.Private) { return true; } // Otherwise, do not include the set accessor in the docs. return false; } */ /// <summary> /// Builds a regular expression that can be used to validate the name of the given type when /// used within a documentation cref attribute. /// </summary> /// <param name="type">The type to match against.</param> /// <returns>Returns the regular expression object.</returns> private static string BuildCrefValidationStringForType(ClassBase type) { Param.AssertNotNull(type, "type"); // We get the actual type name. For nested types this is A.B rather than just B. string actualTypeName = GetActualClassName(type, false); string[] typeNameParts = actualTypeName.Split('.'); StringBuilder actualTypeNameWithoutGenerics = new StringBuilder(); StringBuilder typeNameWithGenerics = new StringBuilder(); for (int i = 0; i < typeNameParts.Length; ++i) { typeNameWithGenerics.Append(BuildTypeNameStringWithGenerics(typeNameParts[i])); actualTypeNameWithoutGenerics.Append(RemoveGenericsFromTypeName(typeNameParts[i])); if (i < typeNameParts.Length - 1) { typeNameWithGenerics.Append(@"\."); actualTypeNameWithoutGenerics.Append(@"\."); } } StringBuilder typeNameWithParamsNumber = new StringBuilder(); for (int i = 0; i < typeNameParts.Length; i++) { typeNameWithParamsNumber.Append(BuildTypeNameStringWithParamsNumber(typeNameParts[i])); if (i < typeNameParts.Length - 1) { typeNameWithParamsNumber.Append(@"\."); } } // Also capture the fully qualified name of the type, without generic parameters included. string namespaceRegex = BuildNamespaceRegex(type); // Build the regex string to match all possible formats for the type name. return string.Format(CultureInfo.InvariantCulture, CrefRegex, namespaceRegex, actualTypeNameWithoutGenerics, typeNameWithGenerics, typeNameWithParamsNumber); }