/// <summary>
        /// Initializes a new instance of the Constructor class.
        /// </summary>
        /// <param name="document">
        /// The document that contains the element.
        /// </param>
        /// <param name="parent">
        /// The parent of the element.
        /// </param>
        /// <param name="header">
        /// The Xml header for this element.
        /// </param>
        /// <param name="attributes">
        /// The list of attributes attached to this element.
        /// </param>
        /// <param name="declaration">
        /// The declaration code for this element.
        /// </param>
        /// <param name="parameters">
        /// The parameters to the constructor.
        /// </param>
        /// <param name="initializerExpression">
        /// The constructor initializer, if there is one.
        /// </param>
        /// <param name="unsafeCode">
        /// Indicates whether the element resides within a block of unsafe code.
        /// </param>
        /// <param name="generated">
        /// Indicates whether the code element was generated or written by hand.
        /// </param>
        internal Constructor(
            CsDocument document, 
            CsElement parent, 
            XmlHeader header, 
            ICollection<Attribute> attributes, 
            Declaration declaration, 
            IList<Parameter> parameters, 
            MethodInvocationExpression initializerExpression, 
            bool unsafeCode, 
            bool generated)
            : base(document, parent, ElementType.Constructor, "constructor " + declaration.Name, header, attributes, declaration, unsafeCode, generated)
        {
            Param.AssertNotNull(document, "document");
            Param.AssertNotNull(parent, "parent");
            Param.Ignore(header);
            Param.Ignore(attributes);
            Param.AssertNotNull(declaration, "declaration");
            Param.AssertNotNull(parameters, "parameters");
            Param.Ignore(initializerExpression);
            Param.Ignore(unsafeCode);
            Param.Ignore(generated);

            // Static constructors are treated as private and handled as a special case for ordering.
            if (this.Declaration.ContainsModifier(CsTokenType.Static))
            {
                this.Declaration.AccessModifierType = AccessModifierType.Private;
            }

            this.parameters = parameters;
            Debug.Assert(parameters.IsReadOnly, "The parameters collection should be read-only.");

            // Add the qualifications
            this.QualifiedName = CodeParser.AddQualifications(this.parameters, this.QualifiedName);

            // If there is an initializer expression, add it to the statement list for this constructor.
            if (initializerExpression != null)
            {
                this.initializer = initializerExpression;

                ConstructorInitializerStatement initializerStatement = new ConstructorInitializerStatement(initializerExpression.Tokens, initializerExpression);
                this.AddStatement(initializerStatement);
            }
        }
        /// <summary>
        /// Checks the given call into Debug.Fail to ensure that it contains a valid debug message.
        /// </summary>
        /// <param name="element">
        /// The parent element.
        /// </param>
        /// <param name="debugFailMethodCall">
        /// The call to Debug.Fail.
        /// </param>
        private void CheckDebugFailMessage(CsElement element, MethodInvocationExpression debugFailMethodCall)
        {
            Param.AssertNotNull(element, "element");
            Param.AssertNotNull(debugFailMethodCall, "debugFailMethodCall");

            // Extract the first argument.
            Argument firstArgument = null;
            foreach (Argument argument in debugFailMethodCall.Arguments)
            {
                firstArgument = argument;
                break;
            }

            if (firstArgument == null || firstArgument.Tokens.First == null)
            {
                // There is no message argument or the message argument is empty.
                this.AddViolation(element, debugFailMethodCall.LineNumber, Rules.DebugFailMustProvideMessageText);
            }
            else if (ArgumentTokensMatchStringEmpty(firstArgument))
            {
                // The message argument contains an empty string or null.
                this.AddViolation(element, debugFailMethodCall.LineNumber, Rules.DebugFailMustProvideMessageText);
            }
        }
        /// <summary>
        /// Checks the given call into Debug.Assert to ensure that it contains a valid debug message.
        /// </summary>
        /// <param name="element">
        /// The parent element.
        /// </param>
        /// <param name="debugAssertMethodCall">
        /// The call to Debug.Assert.
        /// </param>
        private void CheckDebugAssertMessage(CsElement element, MethodInvocationExpression debugAssertMethodCall)
        {
            Param.AssertNotNull(element, "element");
            Param.AssertNotNull(debugAssertMethodCall, "debugAssertMethodCall");

            // Extract the second argument.
            Argument secondArgument = null;
            if (debugAssertMethodCall.Arguments.Count >= 2)
            {
                secondArgument = debugAssertMethodCall.Arguments[1];
            }

            if (secondArgument == null || secondArgument.Tokens.First == null)
            {
                // There is no message argument or the message argument is empty.
                this.AddViolation(element, debugAssertMethodCall.LineNumber, Rules.DebugAssertMustProvideMessageText);
            }
            else if (ArgumentTokensMatchStringEmpty(secondArgument))
            {
                // The message argument contains an empty string or null.
                this.AddViolation(element, debugAssertMethodCall.LineNumber, Rules.DebugAssertMustProvideMessageText);
            }
        }
        /// <summary>
        /// Checks the given code analysis suppression call to ensure that it contains a justification parameter.
        /// </summary>
        /// <param name="element">
        /// The element that contains the suppression attribute.
        /// </param>
        /// <param name="suppression">
        /// The suppression to check.
        /// </param>
        private void CheckCodeAnalysisSuppressionForJustification(CsElement element, MethodInvocationExpression suppression)
        {
            Param.AssertNotNull(element, "element");
            Param.AssertNotNull(suppression, "suppression");

            bool justification = false;
            foreach (Argument argument in suppression.Arguments)
            {
                if (argument.Expression.ExpressionType == ExpressionType.Assignment)
                {
                    AssignmentExpression assignmentExpression = (AssignmentExpression)argument.Expression;
                    if (assignmentExpression.LeftHandSide.Tokens.First.Value.Text.Equals("Justification", StringComparison.Ordinal))
                    {
                        Expression rightHandSide = assignmentExpression.RightHandSide;

                        if (rightHandSide == null || rightHandSide.Tokens == null)
                        {
                            break;
                        }

                        Node<CsToken> rightSideTokenNode = rightHandSide.Tokens.First;
                        if (rightSideTokenNode == null)
                        {
                            break;
                        }

                        if (rightHandSide.ExpressionType == ExpressionType.MemberAccess)
                        {
                            justification = true;
                            break;
                        }

                        if (rightSideTokenNode.Value.CsTokenType == CsTokenType.Other && rightHandSide.ExpressionType == ExpressionType.Literal)
                        {
                            justification = true;
                            break;
                        }

                        if (rightSideTokenNode.Value.CsTokenType == CsTokenType.String && rightSideTokenNode.Value.Text != null
                            && !IsEmptyString(rightSideTokenNode.Value.Text))
                        {
                            justification = true;
                            break;
                        }
                    }
                }
            }

            if (!justification)
            {
                this.AddViolation(element, suppression.LineNumber, Rules.CodeAnalysisSuppressionMustHaveJustification);
            }
        }
        /// <summary>
        /// Determines whether the given method invocation expression contains a code analysis SuppressMessage call.
        /// </summary>
        /// <param name="expression">
        /// The expression.
        /// </param>
        /// <returns>
        /// Returns true if the method is SuppressMessage.
        /// </returns>
        private static bool IsSuppressMessage(MethodInvocationExpression expression)
        {
            Param.AssertNotNull(expression, "expression");

            Node<CsToken> first = expression.Name.Tokens.First;
            if (first != null)
            {
                string text = first.Value.Text;
                if (text.Equals("SuppressMessage", StringComparison.Ordinal) || text.Equals("SuppressMessageAttribute", StringComparison.Ordinal))
                {
                    return true;
                }

                string expressionText = expression.Name.Text;
                if (expressionText.EndsWith(".SuppressMessage", StringComparison.Ordinal)
                    || expressionText.EndsWith(".SuppressMessageAttribute", StringComparison.Ordinal))
                {
                    return true;
                }

                if (text.Equals("System"))
                {
                    if (expression.Name.Tokens.MatchTokens(new[] { "System", ".", "Diagnostics", ".", "CodeAnalysis", ".", "SuppressMessage" })
                        || expression.Name.Tokens.MatchTokens(new[] { "System", ".", "Diagnostics", ".", "CodeAnalysis", ".", "SuppressMessageAttribute" }))
                    {
                        return true;
                    }
                }
            }

            return false;
        }
        /// <summary>
        /// Reads a method access expression.
        /// </summary>
        /// <param name="methodName">
        /// The name of the method being called.
        /// </param>
        /// <param name="previousPrecedence">
        /// The precedence of the previous expression.
        /// </param>
        /// <param name="unsafeCode">
        /// Indicates whether the code being parsed resides in an unsafe code block.
        /// </param>
        /// <returns>
        /// Returns the expression.
        /// </returns>
        private MethodInvocationExpression GetMethodInvocationExpression(Expression methodName, ExpressionPrecedence previousPrecedence, bool unsafeCode)
        {
            Param.AssertNotNull(methodName, "methodName");
            Param.Ignore(previousPrecedence);
            Param.Ignore(unsafeCode);

            MethodInvocationExpression expression = null;
            if (CheckPrecedence(previousPrecedence, ExpressionPrecedence.Primary))
            {
                Reference<ICodePart> expressionReference = new Reference<ICodePart>();

                // The next symbol will be the opening parenthesis.
                Bracket openParenthesis = this.GetBracketToken(CsTokenType.OpenParenthesis, SymbolType.OpenParenthesis, expressionReference);
                Node<CsToken> openParenthesisNode = this.tokens.InsertLast(openParenthesis);

                // Get the argument list now.
                IList<Argument> argumentList = this.GetArgumentList(SymbolType.CloseParenthesis, expressionReference, unsafeCode);

                // Get the closing parenthesis.
                Bracket closeParenthesis = this.GetBracketToken(CsTokenType.CloseParenthesis, SymbolType.CloseParenthesis, expressionReference);
                Node<CsToken> closeParenthesisNode = this.tokens.InsertLast(closeParenthesis);

                openParenthesis.MatchingBracketNode = closeParenthesisNode;
                closeParenthesis.MatchingBracketNode = openParenthesisNode;

                // Pull out the first token from the method name.
                Debug.Assert(methodName.Tokens.First != null, "The method name should not be empty");
                Node<CsToken> firstTokenNode = methodName.Tokens.First;

                // Create the token list for the method invocation expression.
                CsTokenList partialTokens = new CsTokenList(this.tokens, firstTokenNode, this.tokens.Last);

                // Create and return the expression.
                expression = new MethodInvocationExpression(partialTokens, methodName, argumentList);
                expressionReference.Target = expression;
            }

            return expression;
        }
        /// <summary>
        /// Checks a method invocation expression to make that the parameters are positioned correctly.
        /// </summary>
        /// <param name="element">The element containing the expression.</param>
        /// <param name="expression">The expression to check.</param>
        private void CheckMethodInvocationParameters(Element element, MethodInvocationExpression expression)
        {
            Param.AssertNotNull(element, "element");
            Param.AssertNotNull(expression, "expression");

            if (!expression.Generated)
            {
                ArgumentList argumentList = expression.ArgumentList;
                if (argumentList != null)
                {
                    this.CheckParameters(element, argumentList, new ArgumentListWrapper(argumentList), expression.LineNumber, TokenType.OpenParenthesis, TokenType.CloseParenthesis);
                }
            }
        }
        /// <summary>
        /// Extracts the CheckID for the rule being suppressed, from the given Code Analysis SuppressMessage attribute expression.
        /// </summary>
        /// <param name="codeAnalysisAttributeExpression">
        /// The expression to parse.
        /// </param>
        /// <param name="ruleId">
        /// Returns the rule ID.
        /// </param>
        /// <param name="ruleName">
        /// Returns the rule name.
        /// </param>
        /// <param name="ruleNamespace">
        /// Returns the namespace that contains the rule.
        /// </param>
        /// <returns>
        /// Returns true if the ID, name, and namespace were successfully extracted from the suppression.
        /// </returns>
        private static bool TryCrackCodeAnalysisSuppression(
            MethodInvocationExpression codeAnalysisAttributeExpression, out string ruleId, out string ruleName, out string ruleNamespace)
        {
            Param.AssertNotNull(codeAnalysisAttributeExpression, "codeAnalysisAttributeExpression");

            // Initialize all out fields to null.
            ruleId = ruleName = ruleNamespace = null;

            if (codeAnalysisAttributeExpression.Arguments != null && codeAnalysisAttributeExpression.Arguments.Count >= 2)
            {
                // The rule namespace sits in the first argument.
                ruleNamespace = ExtractStringFromAttributeExpression(codeAnalysisAttributeExpression.Arguments[0].Expression);
                if (string.IsNullOrEmpty(ruleNamespace))
                {
                    return false;
                }

                // Here we support the old SupressMessage format for Microsoft.StyleCop
                if (ruleNamespace.StartsWith("Microsoft.StyleCop."))
                {
                    ruleNamespace = ruleNamespace.Substring("Microsoft.".Length);
                }

                // The checkID and rule name sit in the second argument.
                string nameAndId = ExtractStringFromAttributeExpression(codeAnalysisAttributeExpression.Arguments[1].Expression);
                if (string.IsNullOrEmpty(nameAndId))
                {
                    return false;
                }

                // When the nameAndId field just contains a *, this means to supress all rules in the given namespace.
                if (nameAndId == "*")
                {
                    ruleId = "*";
                    return true;
                }

                // Split the rule name and ID.
                int separatorIndex = nameAndId.IndexOf(':');
                if (separatorIndex == -1)
                {
                    return false;
                }

                ruleId = nameAndId.Substring(0, separatorIndex);
                ruleName = nameAndId.Substring(separatorIndex + 1, nameAndId.Length - separatorIndex - 1);

                return ruleId.Length > 0 && ruleName.Length > 0;
            }

            return false;
        }
        /// <summary>
        /// Checks the given call into Debug.Fail to ensure that it contains a valid debug message.
        /// </summary>
        /// <param name="element">The parent element.</param>
        /// <param name="debugFailMethodCall">The call to Debug.Fail.</param>
        private void CheckDebugFailMessage(Element element, MethodInvocationExpression debugFailMethodCall)
        {
            Param.AssertNotNull(element, "element");
            Param.AssertNotNull(debugFailMethodCall, "debugFailMethodCall");

            // Extract the first argument.
            Argument firstArgument = null;
            if (debugFailMethodCall.ArgumentList != null && debugFailMethodCall.ArgumentList.Count > 0)
            {
                firstArgument = debugFailMethodCall.ArgumentList[0];
            }

            if (firstArgument == null)
            {
                // There is no message argument.
                this.AddViolation(element, debugFailMethodCall.LineNumber, Rules.DebugFailMustProvideMessageText);
            }
            else
            {
                Token firstArgumentStartToken = firstArgument.FindFirstDescendentToken();    
                if (firstArgumentStartToken == null)
                {
                    // The message argument is empty.
                    this.AddViolation(element, debugFailMethodCall.LineNumber, Rules.DebugFailMustProvideMessageText);
                }
                else if (firstArgumentStartToken.TokenType == TokenType.String && IsEmptyString(firstArgumentStartToken.Text))
                {
                    // The message argument contains an empty string.
                    this.AddViolation(element, debugFailMethodCall.LineNumber, Rules.DebugFailMustProvideMessageText);
                }
            }
        }
        /// <summary>
        /// Checks the given code analysis suppression call to ensure that it contains a justifiction argument.
        /// </summary>
        /// <param name="element">The element that contains the suppression attribute.</param>
        /// <param name="suppression">The suppression to check.</param>
        private void CheckCodeAnalysisSuppressionForJustification(Element element, MethodInvocationExpression suppression)
        {
            Param.AssertNotNull(element, "element");
            Param.AssertNotNull(suppression, "suppression");

            bool justifiction = false;
            if (suppression.ArgumentList != null)
            {
                foreach (Argument argument in suppression.ArgumentList.Arguments)
                {
                    if (argument.Expression.ExpressionType == ExpressionType.Assignment)
                    {
                        AssignmentExpression assignmentExpression = (AssignmentExpression)argument.Expression;
                        Token firstAssignmentExpressionToken = assignmentExpression.LeftHandSide.FindFirstDescendentToken();
                        if (firstAssignmentExpressionToken != null && firstAssignmentExpressionToken.Text.Equals("Justification", StringComparison.Ordinal))
                        {
                            Token rightSideToken = assignmentExpression.RightHandSide.FindFirstDescendentToken();
                            if (rightSideToken != null &&
                                rightSideToken.TokenType == TokenType.String &&
                                rightSideToken.Text != null &&
                                !IsEmptyString(rightSideToken.Text))
                            {
                                justifiction = true;
                                break;
                            }
                        }
                    }
                }

                if (!justifiction)
                {
                    this.AddViolation(element, suppression.LineNumber, Rules.CodeAnalysisSuppressionMustHaveJustification);
                }
            }
        }
        /// <summary>
        /// Determines whether the given method invocation expression contains a code analysis SuppressMessage call.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <returns>Returns true if the method is SuppressMessage.</returns>
        private static bool IsSuppressMessage(MethodInvocationExpression expression)
        {
            Param.AssertNotNull(expression, "expression");

            Token first = expression.Name.FindFirstDescendentToken();
            if (first != null)
            {
                if (first.Text.Equals("SuppressMessage", StringComparison.Ordinal))
                {
                    return true;
                }
                
                if (first.Text.Equals("System"))
                {
                    return expression.Name.MatchTokensFrom(first, "System", ".", "Diagnostics", ".", "CodeAnalysis", ".", "SuppressMessage");
                }
            }

            return false;
        }
        /// <summary>
        /// The save.
        /// </summary>
        /// <param name="methodInvocationExpression">
        /// The method invocation expression.
        /// </param>
        private void Save(MethodInvocationExpression methodInvocationExpression)
        {
            if (methodInvocationExpression.Name.Text == "base")
            {
                this.Save(this.currentBaseClass, this.cppWriter, SavingOptions.RemovePointer);
            }
            else
            {
                if (methodInvocationExpression.Name is LiteralExpression
                    && methodInvocationExpression.Name.Text.Contains('<'))
                {
                    // you need to process generic types
                    this.Save(methodInvocationExpression.Name.Text, this.cppWriter, SavingOptions.RemovePointer);
                }
                else
                {
                    @switch(methodInvocationExpression.Name);
                }
            }

            this.cppWriter.Write("(");
            @switch(methodInvocationExpression.Arguments);
            this.cppWriter.Write(")");
        }
Example #13
0
        /// <summary>
        /// Extracts the CheckID for the rule being suppressed, from the given Code Analysis SuppressMessage attribute expression.
        /// </summary>
        /// <param name="codeAnalysisAttributeExpression">The expression to parse.</param>
        /// <param name="ruleId">Returns the rule ID.</param>
        /// <param name="ruleName">Returns the rule name.</param>
        /// <param name="ruleNamespace">Returns the namespace that contains the rule.</param>
        /// <returns>Returns true if the ID, name, and namespace were successfully extracted from the suppression.</returns>
        private static bool TryCrackCodeAnalysisSuppression(MethodInvocationExpression codeAnalysisAttributeExpression, out string ruleId, out string ruleName, out string ruleNamespace)
        {
            Param.AssertNotNull(codeAnalysisAttributeExpression, "codeAnalysisAttributeExpression");

            // Initialize all out fields to null.
            ruleId = ruleName = ruleNamespace = null;

            if (codeAnalysisAttributeExpression.ArgumentList != null && codeAnalysisAttributeExpression.ArgumentList.Count >= 2)
            {
                // The rule namespace sits in the first argument.
                ruleNamespace = ExtractStringFromAttributeExpression(codeAnalysisAttributeExpression.ArgumentList[0].Expression);
                if (string.IsNullOrEmpty(ruleNamespace))
                {
                    return false;
                }

                // The checkID and rule name sit in the second argument.
                string nameAndId = ExtractStringFromAttributeExpression(codeAnalysisAttributeExpression.ArgumentList[1].Expression);
                if (string.IsNullOrEmpty(nameAndId))
                {
                    return false;
                }

                int separatorIndex = nameAndId.IndexOf(':');
                if (separatorIndex == -1)
                {
                    return false;
                }

                ruleId = nameAndId.Substring(0, separatorIndex);
                ruleName = nameAndId.Substring(separatorIndex + 1, nameAndId.Length - separatorIndex - 1);

                return ruleId.Length > 0 && ruleName.Length > 0;
            }

            return false;
        }
        /// <summary>
        /// Checks a method invocation expression to make that the parameters are positioned correctly.
        /// </summary>
        /// <param name="element">
        /// The element containing the expression.
        /// </param>
        /// <param name="expression">
        /// The expression to check.
        /// </param>
        private void CheckMethodInvocationParameters(CsElement element, MethodInvocationExpression expression)
        {
            Param.AssertNotNull(element, "element");
            Param.AssertNotNull(expression, "expression");

            if (expression.Tokens.First != null && !expression.Tokens.First.Value.Generated)
            {
                ArgumentList argumentList = new ArgumentList(expression.Arguments);
                CsTokenList argumentListTokens = GetArgumentListTokens(
                    expression.Tokens, expression.Name.Tokens.Last, CsTokenType.OpenParenthesis, CsTokenType.CloseParenthesis);

                if (argumentListTokens != null)
                {
                    this.CheckParameters(
                        element, 
                        argumentListTokens, 
                        argumentList, 
                        expression.LineNumber, 
                        CsTokenType.OpenParenthesis, 
                        CsTokenType.CloseParenthesis, 
                        element.FriendlyTypeText);
                }
            }
        }
        /// <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.
                        if (argument.Expression.ExpressionType != ExpressionType.MethodInvocation)
                        {
                            this.CheckClassMemberRulesForExpression(argument.Expression, null, parentElement, parentClass, members);
                        }
                    }
                }
            }
        }