Click here to Skip to main content
15,881,204 members
Articles / Programming Languages / C#

Calculating Metrics and Searching with a CodeDOM (Part 8)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
6 Mar 2013CDDL7 min read 21.9K   682   10  
Calculating metrics on and searching a CodeDOM.
// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using Nova.Analysis;
using Nova.Parsing;
using Nova.Rendering;
using Nova.Resolving;

namespace Nova.CodeDOM
{
    /// <summary>
    /// The common base class of all code objects.
    /// </summary>
    public abstract class CodeObject : ICloneable, IDisposable
    {
        #region /* STATIC FIELDS */

        /// <summary>
        /// The tab size used for indentation of code.
        /// </summary>
        public static int TabSize = 4;

        /// <summary>
        /// Automatically detect and preserve tabs used for code indentation.
        /// </summary>
        public static bool AutoDetectTabs = true;

        /// <summary>
        /// Use tabs instead of spaces for indentation (ignored if AutoDetectTabs is true).
        /// </summary>
        public static bool UseTabs;

        /// <summary>
        /// The maximum line length used for automatic formatting, such as code alignment and line wrapping.
        /// </summary>
        public static int MaximumLineLength = 128;

        /// <summary>
        /// Determines whether or not formatting cleanup is automatically performed during the parsing process.
        /// </summary>
        public static bool AutomaticFormattingCleanup;

        /// <summary>
        /// Determines whether or not code cleanup is automatically performed during the parsing and/or resolving process.
        /// </summary>
        public static bool AutomaticCodeCleanup;

        #endregion

        #region /* FIELDS */

        /// <summary>
        /// The parent <see cref="CodeObject"/>.
        /// </summary>
        protected CodeObject _parent;

        /// <summary>
        /// Any <see cref="Annotation"/>s (<see cref="Comment"/>s, <see cref="DocComment"/>s, <see cref="Attribute"/>s,
        /// or <see cref="Message"/>s) associated with the <see cref="CodeObject"/> (null if none).
        /// </summary>
        /// <remarks>
        /// Annotations are generally supported on all code objects, although <see cref="Attribute"/>s are only legal on certain objects,
        /// and comments on <see cref="Expression"/>s that aren't rendered as <see cref="Statement"/>s (i.e. sub-expressions) will be rendered
        /// as inline comments if not EOL (and in some cases might only be visible in the GUI as pop-ups).
        /// </remarks>
        protected ChildList<Annotation> _annotations;

        /// <summary>
        /// The starting line number associated with the <see cref="CodeObject"/>.
        /// </summary>
        protected int _lineNumber;

        /// <summary>
        /// The starting column number associated with the <see cref="CodeObject"/>.
        /// </summary>
        protected ushort _columnNumber;

        /// <summary>
        /// Formatting flags - for line feeds, braces, etc.
        /// </summary>
        protected FormatFlags _formatFlags;

        #endregion

        #region /* CONSTRUCTORS */

        protected CodeObject()
        {
            if (IsFirstOnLineDefault)
                SetNewLines(1);
        }

        /// <summary>
        /// Create a code object from an existing one, copying members.
        /// </summary>
        protected CodeObject(CodeObject codeObject)
        {
            Parent = codeObject.Parent;
            Annotations = codeObject.Annotations;
            codeObject.Annotations = null;
            CopyFormatting(codeObject);
        }

        #endregion

        #region /* STATIC CONSTRUCTOR */

        static CodeObject()
        {
            // Override any default static field values with any specified in the config file
            Configuration.LoadSettings();

            // Initialize static TypeRefs to defaults
            TypeRef.InitializeTypeRefs();

            // Initialize all parse-points for all CodeDOM objects
            InitializeParsePoints();
        }

        internal static void InitializeParsePoints()
        {
            // Force calls to all static AddParsePoints methods on all types derived from CodeObject, so that
            // all parse-points will be registered with the parser before it tries to parse something.
            foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
            {
                if (type.IsSubclassOf(typeof(CodeObject)))
                {
                    MethodInfo method = type.GetMethod("AddParsePoints", BindingFlags.NonPublic | BindingFlags.Static);
                    if (method != null)
                        method.Invoke(null, null);
                }
            }
        }

        /// <summary>
        /// Call this method to force a reference to <see cref="CodeObject"/>, so that all static members are
        /// initialized, and settings are read from the config file.  Call this before changing any static
        /// settings manually, or your changes will be overwritten when the config file is processed.
        /// </summary>
        public static void ForceReference()
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The parent <see cref="CodeObject"/>.
        /// </summary>
        public virtual CodeObject Parent
        {
            get { return _parent; }
            set
            {
                // Do nothing unless the new value is different from the current one
                if (_parent != value)
                {
                    // If the parent is already set, remove any listed annotations first
                    if (_annotations != null && _parent != null)
                    {
                        foreach (Annotation annotation in _annotations)
                        {
                            if (annotation.IsListed)
                                NotifyListedAnnotationRemoved(annotation);
                        }
                    }

                    _parent = value;

                    // If the new value is non-null, propagate any listed annotations
                    if (_annotations != null && value != null)
                    {
                        foreach (Annotation annotation in _annotations)
                        {
                            if (annotation.IsListed)
                                NotifyListedAnnotationAdded(annotation);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// The line number associated with the <see cref="CodeObject"/> (if any, 0 if none).
        /// </summary>
        /// <remarks>
        /// The line number will match the input file when the object is parsed, but may differ if the code tree is modified.
        /// </remarks>
        public virtual int LineNumber
        {
            get { return _lineNumber; }
        }

        /// <summary>
        /// The column number associated with the <see cref="CodeObject"/> (if any, 0 if none).
        /// </summary>
        /// <remarks>
        /// The column will match the input file when the object is parsed, but may differ if the code tree is modified.
        /// </remarks>
        public virtual int ColumnNumber
        {
            get { return _columnNumber; }
        }

        /// <summary>
        /// Any hidden reference to another <see cref="CodeObject"/>.
        /// </summary>
        /// <remarks>
        /// A <see cref="NewObject"/> or <see cref="ConstructorInitializer"/> will have a hidden <see cref="ConstructorRef"/>,
        /// indexers can have a hidden <see cref="IndexerRef"/>, overloaded operators will have a hidden <see cref="OperatorRef"/>,
        /// and a 'goto case {constant}' will have a hidden <see cref="GotoTargetRef"/>.
        /// </remarks>
        public virtual SymbolicRef HiddenRef
        {
            get { return null; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Set a field of a code object, including setting the parent, and optional formatting.
        /// </summary>
        protected void SetField<T>(ref T field, T value, bool format) where T : CodeObject
        {
            if (field != null)
                field.Dispose();
            field = value;
            if (value != null)
            {
                field.Parent = this;
                if (format)
                    DefaultFormatField(field);
            }
        }

        /// <summary>
        /// Set a field of a code object, including setting the parent, and optional formatting.
        /// </summary>
        protected void SetField(ref object field, object value, bool format)
        {
            if (field is CodeObject)
                ((CodeObject)field).Dispose();
            field = value;
            if (value != null)
            {
                if (field is CodeObject)
                {
                    CodeObject codeObject = (CodeObject)field;
                    codeObject.Parent = this;
                    if (format)
                        DefaultFormatField(codeObject);
                }
            }
        }

        /// <summary>
        /// Set a ChildList collection field of a code object, including setting the parent.
        /// </summary>
        protected void SetField<T>(ref ChildList<T> collectionField, ChildList<T> newCollection) where T : CodeObject
        {
            // Set the parent of any existing collection to null
            if (collectionField != null)
                collectionField.Parent = null;

            collectionField = newCollection;

            // Set the parent of the new collection to the current object
            if (newCollection != null)
                newCollection.Parent = this;
        }

        /// <summary>
        /// Clone a field of a code object, including setting the parent.
        /// </summary>
        protected void CloneField<T>(ref T field, T value) where T : CodeObject
        {
            if (value != null)
            {
                field = (T)value.Clone();
                field.Parent = this;
            }
        }

        /// <summary>
        /// Clone a field of a code object, including setting the parent.
        /// </summary>
        protected void CloneField(ref object field, object value)
        {
            if (field is CodeObject && value != null)
            {
                field = ((CodeObject)value).Clone();
                ((CodeObject)field).Parent = this;
            }
        }

        /// <summary>
        /// Create a reference to the <see cref="CodeObject"/>.
        /// </summary>
        /// <param name="isFirstOnLine">True if the reference should be displayed on a new line.</param>
        /// <returns>The appropriate type of reference.</returns>
        public virtual SymbolicRef CreateRef(bool isFirstOnLine)
        {
            throw new Exception("References aren't supported for a code object of type: " + GetType());
        }

        /// <summary>
        /// Create a reference to the <see cref="CodeObject"/>.
        /// </summary>
        /// <returns>The appropriate type of reference.</returns>
        public SymbolicRef CreateRef()
        {
            return CreateRef(false);
        }

        /// <summary>
        /// Find the parent object of the specified type.
        /// </summary>
        public T FindParent<T>() where T : CodeObject
        {
            CodeObject obj = this;
            do
                obj = obj.Parent;
            while (obj != null && !(obj is T));
            return (obj != null ? (T)obj : null);
        }

        /// <summary>
        /// Find the parent method or anonymous method of the current code object.
        /// </summary>
        /// <returns>The parent <see cref="MethodDeclBase"/> or <see cref="AnonymousMethod"/>, or null if none found.</returns>
        public CodeObject FindParentMethod()
        {
            CodeObject obj = this;
            do
                obj = obj.Parent;
            while (obj != null && !(obj is MethodDeclBase || obj is AnonymousMethod));
            return obj;
        }

        /// <summary>
        /// Get an enumerator for all children objects of type <typeparamref name="T"/> in
        /// the <see cref="CodeObject"/> and in any child CodeObjects (recursively).
        /// </summary>
        /// <typeparam name="T">May be <see cref="CodeObject"/> to return all objects, or a derived
        /// type to return only objects of that type (or further derived types).</typeparam>
        public IEnumerable<T> GetAllChildren<T>() where T : CodeObject
        {
            return Enumerable.Select<Result, T>(Enumerable.Where(new FindByType(typeof(T), this).Find(),
                delegate(Result result) { return result.CodeObject != this; }), delegate(Result result) { return (T)result.CodeObject; });
        }

        /// <summary>
        /// Get the <see cref="Namespace"/> for this <see cref="CodeObject"/>.
        /// </summary>
        public Namespace GetNamespace()
        {
            NamespaceDecl parentNamespaceDecl = FindParent<NamespaceDecl>();
            return (parentNamespaceDecl != null ? parentNamespaceDecl.Namespace : null);
        }

        /// <summary>
        /// Get the indent level of this object.
        /// </summary>
        public virtual int GetIndentLevel()
        {
            // We can only be indented if we have a parent
            if (_parent != null)
            {
                // Start with the parent's indent level, and add one if the parent says we're indented
                int indentLevel = _parent.GetIndentLevel();
                if (_parent.IsChildIndented(this))
                    ++indentLevel;
                return indentLevel;
            }
            return 0;
        }

        /// <summary>
        /// Returns true if the specified child object is indented from the parent.
        /// </summary>
        protected virtual bool IsChildIndented(CodeObject obj)
        {
            // The child object can only be indented if it's the first thing on the line
            if (obj.IsFirstOnLine)
            {
                // By default, any child object on a new line that isn't a prefix should be indented
                return !IsChildPrefix(obj);
            }
            return false;
        }

        /// <summary>
        /// Returns true if the specified child object is prefixed to the current object.
        /// </summary>
        protected bool IsChildPrefix(CodeObject obj)
        {
            return (_annotations != null && Enumerable.Any(_annotations, delegate(Annotation annotation) { return annotation == obj; }));
        }

        /// <summary>
        /// Get the current indent in spaces.
        /// </summary>
        public int GetIndentSpaceCount()
        {
            return GetIndentLevel() * TabSize;
        }

        /// <summary>
        /// Set the line and column numbers to those in the specified <see cref="CodeObject"/>.
        /// </summary>
        protected internal void SetLineCol(CodeObject codeObject)
        {
            _lineNumber = codeObject._lineNumber;
            _columnNumber = codeObject._columnNumber;
        }

        /// <summary>
        /// Set the line and column numbers to those in the specified token.
        /// </summary>
        protected internal void SetLineCol(Token token)
        {
            _lineNumber = token.LineNumber;
            _columnNumber = token.ColumnNumber;
        }

        /// <summary>
        /// Explicit interface implementation of Clone()
        /// </summary>
        object ICloneable.Clone()
        {
            // Delegate to virtual Clone() method below
            return Clone();
        }

        /// <summary>
        /// Deep-clone the code object.
        /// </summary>
        public virtual CodeObject Clone()
        {
            // Perform a shallow copy - any references in the object MUST be nulled or cloned manually
            // by an overridden version of this method in derived classes!
            CodeObject clone = (CodeObject)MemberwiseClone();
            // Clone annotations so we get comments, attributes, and directives - but clear any messages
            clone._annotations = ChildListHelpers.Clone(_annotations, clone);
            clone.RemoveAllMessages(MessageSource.Unspecified, false);
            // Clear the parent reference - do NOT use the property, or the child collection of a Block
            // will have its Parent set to null in the *source* collection since we have shallow-copied
            // references and not cloned them yet.
            clone._parent = null;
            return clone;
        }

        /// <summary>
        /// Dispose the <see cref="CodeObject"/>.
        /// </summary>
        public virtual void Dispose()
        {
            // Clear the parent reference if disposed.  Although this is technically not necessary in order for
            // the object to be garbage-collected, it's still a very good idea in order to help discover coding
            // errors where objects are manipulated after becoming obsolete.
            _parent = null;
        }

        #endregion

        #region /* ANNOTATIONS */

        /// <summary>
        /// Create the list of child <see cref="Annotation"/>s, or return the existing one.
        /// </summary>
        public ChildList<Annotation> CreateAnnotations()
        {
            if (_annotations == null)
                _annotations = new ChildList<Annotation>(this);
            return _annotations;
        }

        /// <summary>
        /// Annotations (comments, attributes, directives, messages) associated with the current code object.
        /// </summary>
        public ChildList<Annotation> Annotations
        {
            get { return _annotations; }
            set { SetField(ref _annotations, value); }
        }

        /// <summary>
        /// True if the code object has any annotations.
        /// </summary>
        public bool HasAnnotations
        {
            get { return (_annotations != null && _annotations.Count > 0); }
        }

        /// <summary>
        /// True if the code object has any comments of any kind.
        /// </summary>
        public bool HasComments
        {
            get { return HasAnnotation<CommentBase>(); }
        }

        /// <summary>
        /// True if the code object has any EOL comments.
        /// </summary>
        public bool HasEOLComments
        {
            get { return (_annotations != null && Enumerable.Any(_annotations,
                delegate(Annotation annotation) { return annotation is Comment && annotation.IsEOL && !annotation.IsInfix; })); }
        }

        /// <summary>
        /// True if the code object has any regular (non-doc) preceeding (non-EOL, non-Infix, non-Postfix) comments.
        /// </summary>
        public bool HasNonEOLComments
        {
            get { return (_annotations != null && Enumerable.Any(_annotations,
                delegate(Annotation annotation) { return annotation is Comment && !annotation.IsEOL && !annotation.IsInfix && !annotation.IsPostfix; })); }
        }

        /// <summary>
        /// True if the code object has any EOL or Postfix annotations.
        /// </summary>
        public bool HasEOLOrPostAnnotations
        {
            get { return (_annotations != null && Enumerable.Any(_annotations,
                delegate(Annotation annotation) { return ((annotation is Comment && annotation.IsEOL) || annotation.IsPostfix) && !annotation.IsInfix; })); }
        }

        /// <summary>
        /// True if the code object has any documentation comments.
        /// </summary>
        public bool HasDocComments
        {
            get { return HasAnnotation<DocComment>(); }
        }

        /// <summary>
        /// True if the code object has any Infix comments.
        /// </summary>
        public bool HasInfixComments
        {
            get { return (_annotations != null && Enumerable.Any(_annotations, delegate(Annotation annotation) { return annotation is Comment && annotation.IsInfix; })); }
        }

        /// <summary>
        /// True if the code object has any attributes.
        /// </summary>
        public bool HasAttributes
        {
            get { return HasAnnotation<Attribute>(); }
        }

        /// <summary>
        /// True if the code object has any compiler directive annotations.
        /// </summary>
        public bool HasCompilerDirectives
        {
            get { return HasAnnotation<CompilerDirective>(); }
        }

        /// <summary>
        /// True if the code object has any postfix annotations.
        /// </summary>
        public bool HasPostAnnotations
        {
            get { return (_annotations != null && Enumerable.Any(_annotations, delegate(Annotation annotation) { return annotation.IsPostfix; })); }
        }

        /// <summary>
        /// True if the code object has any annotations on separate lines.
        /// </summary>
        public bool HasFirstOnLineAnnotations
        {
            get { return (_annotations != null && Enumerable.Any(_annotations, delegate(Annotation annotation) { return !(annotation is Message) && annotation.IsFirstOnLine; })); }
        }

        /// <summary>
        /// True if the code object has any generated messages.
        /// </summary>
        public bool HasMessages
        {
            get { return HasAnnotation<Message>(); }
        }

        /// <summary>
        /// True if the code object has any error messages.
        /// </summary>
        public bool HasErrors
        {
            get { return (_annotations != null && Enumerable.Any(_annotations,
                delegate(Annotation annotation) { return annotation is Message && ((Message)annotation).Severity == MessageSeverity.Error; })); }
        }

        protected bool HasAnnotation<T>() where T : Annotation
        {
            return (_annotations != null && Enumerable.Any(Enumerable.OfType<T>(_annotations)));
        }

        /// <summary>
        /// The comment for the code object (if any).
        /// </summary>
        /// <remarks>
        /// This property allows for the very convenient setting of comments in object initializers.
        /// Although there is support for multiple comments on the same object, this property doesn't
        /// support that, returning the first one that it finds, and replacing all existing ones when set.
        /// </remarks>
        public string Comment
        {
            get
            {
                // Just return the first (non-EOL, non-Postfix) comment if there is more than one
                if (_annotations != null)
                {
                    Comment comment = (Comment)Enumerable.FirstOrDefault(_annotations,
                        delegate(Annotation annotation) { return annotation is Comment && !annotation.IsEOL && !annotation.IsPostfix; });
                    if (comment != null)
                        return comment.Text;
                }
                return null;
            }
            set
            {
                // Remove all existing (non-EOL) comments before adding the new one
                if (_annotations != null)
                    _annotations.RemoveAll(delegate(Annotation annotation) { return annotation is Comment && !annotation.IsEOL && !annotation.IsPostfix; });
                if (value != null)
                    AttachComment(value);
            }
        }

        /// <summary>
        /// The documentation comment for the code object (if any).
        /// </summary>
        /// <remarks>
        /// This property allows for the very convenient setting of documentation comments in object initializers.
        /// Although there is support for multiple documentation comments on the same object, this property doesn't
        /// support that, returning the first one that it finds, and replacing all existing ones when set.
        /// </remarks>
        public DocComment DocComment
        {
            get
            {
                // Just return the first documentation comment if there is more than one
                if (_annotations != null)
                {
                    DocComment comment = (DocComment)Enumerable.FirstOrDefault(_annotations, delegate(Annotation annotation) { return annotation is DocComment; });
                    if (comment != null)
                        return comment;
                }
                return null;
            }
            set
            {
                // Remove all existing documentation comments before adding the new one
                if (_annotations != null)
                    _annotations.RemoveAll(delegate(Annotation annotation) { return annotation is DocComment; });
                if (value != null)
                    AttachAnnotation(value);
            }
        }

        /// <summary>
        /// The End-Of-Line comment for the code object (if any).
        /// </summary>
        /// <remarks>
        /// This property allows for the very convenient setting of EOL comments in object initializers.
        /// Although there is support for multiple EOL comments on the same object, this property doesn't
        /// support that, returning the first one that it finds, and replacing all existing ones when set.
        /// </remarks>
        public string EOLComment
        {
            get
            {
                // Just return the first (non-Infix) EOL comment if there is more than one
                if (_annotations != null)
                {
                    Comment comment = (Comment)Enumerable.FirstOrDefault(_annotations, delegate(Annotation annotation) { return annotation is Comment && annotation.IsEOL && !annotation.IsInfix; });
                    if (comment != null)
                        return comment.Text;
                }
                return null;
            }
            set
            {
                // Remove all existing EOL comments before adding the new one
                if (_annotations != null)
                    _annotations.RemoveAll(delegate(Annotation annotation) { return annotation is Comment && annotation.IsEOL && !annotation.IsInfix; });
                if (value != null)
                    AttachEOLComment(value);
            }
        }

        /// <summary>
        /// The infix comment for the code object (if any).
        /// </summary>
        /// <remarks>
        /// This property allows for the very convenient setting of infix comments in object initializers.
        /// Although there is support for multiple infix comments on the same object, this property doesn't
        /// support that, returning the first one that it finds, and replacing all existing ones when set.
        /// </remarks>
        public string InfixComment
        {
            get
            {
                // Just return the first infix comment if there is more than one
                if (_annotations != null)
                {
                    Comment comment = (Comment)Enumerable.FirstOrDefault(_annotations, delegate(Annotation annotation) { return annotation is Comment && annotation.IsInfix; });
                    if (comment != null)
                        return comment.Text;
                }
                return null;
            }
            set
            {
                // Remove all existing infix comments before adding the new one
                if (_annotations != null)
                    _annotations.RemoveAll(delegate(Annotation annotation) { return annotation is Comment && annotation.IsInfix; });
                if (value != null)
                    AttachComment(value, AnnotationFlags.IsInfix1);
            }
        }

        /// <summary>
        /// The postfix comment for the code object (if any).
        /// </summary>
        /// <remarks>
        /// This property allows for the very convenient setting of postfix comments in object initializers.
        /// Although there is support for multiple postfix comments on the same object, this property doesn't
        /// support that, returning the first one that it finds, and replacing all existing ones when set.
        /// </remarks>
        public string PostfixComment
        {
            get
            {
                // Just return the first postfix comment if there is more than one
                if (_annotations != null)
                {
                    Comment comment = (Comment)Enumerable.FirstOrDefault(_annotations, delegate(Annotation annotation) { return annotation is Comment && annotation.IsPostfix; });
                    if (comment != null)
                        return comment.Text;
                }
                return null;
            }
            set
            {
                // Remove all existing postfix comments before adding the new one
                if (_annotations != null)
                    _annotations.RemoveAll(delegate(Annotation annotation) { return annotation is Comment && annotation.IsPostfix; });
                if (value != null)
                    AttachComment(value, AnnotationFlags.IsPostfix);
            }
        }

        /// <summary>
        /// Create a comment object and attach it to the code object.
        /// </summary>
        public void AttachComment(string comment, AnnotationFlags annotationFlags, CommentFlags commentFlags)
        {
            Comment commentObj = new Comment(comment, commentFlags);
            // Default any infix comments to NOT first-on-line
            if ((annotationFlags & AnnotationFlags.InfixMask) != 0)
                commentObj.IsFirstOnLine = false;
            AttachAnnotation(commentObj, annotationFlags);
        }

        /// <summary>
        /// Create a comment object and attach it to the code object.
        /// </summary>
        public void AttachComment(string comment, AnnotationFlags annotationFlags)
        {
            AttachComment(comment, annotationFlags, CommentFlags.None);
        }

        /// <summary>
        /// Create a comment object and attach it to the code object.
        /// </summary>
        public void AttachComment(string comment)
        {
            AttachComment(comment, AnnotationFlags.None, CommentFlags.None);
        }

        /// <summary>
        /// Create an EOL comment object and attach it to the code object.
        /// </summary>
        public void AttachEOLComment(string comment)
        {
            AttachAnnotation(new Comment(comment, CommentFlags.EOL));
        }

        /// <summary>
        /// Create a message and attach it to the code object.
        /// </summary>
        public virtual void AttachMessage(string text, MessageSeverity messageType, MessageSource messageSource)
        {
            if (messageType != MessageSeverity.Unspecified)
                AttachAnnotation(new Message(text, messageType, messageSource));
        }

        /// <summary>
        /// Attach an <see cref="Annotation"/> to the <see cref="CodeObject"/> at the specified position.
        /// </summary>
        /// <param name="annotation">The annotation.</param>
        /// <param name="position">The position at which to place it.</param>
        /// <param name="atFront">Inserts at the front of any existing annotations if true, otherwise adds at the end.</param>
        public void AttachAnnotation(Annotation annotation, AnnotationFlags position, bool atFront)
        {
            annotation.AnnotationFlags |= position;
            AttachAnnotation(annotation, atFront);
        }

        /// <summary>
        /// Attach an <see cref="Annotation"/> to the <see cref="CodeObject"/> at the specified position.
        /// </summary>
        /// <param name="annotation">The annotation.</param>
        /// <param name="position">The position at which to place it.</param>
        public void AttachAnnotation(Annotation annotation, AnnotationFlags position)
        {
            annotation.AnnotationFlags |= position;
            AttachAnnotation(annotation);
        }

        /// <summary>
        /// Attach an <see cref="Annotation"/> (<see cref="Comment"/>, <see cref="DocComment"/>, <see cref="Attribute"/>, <see cref="CompilerDirective"/>, or <see cref="Message"/>) to the <see cref="CodeObject"/>.
        /// </summary>
        /// <param name="annotation">The <see cref="Annotation"/>.</param>
        /// <param name="atFront">Inserts at the front if true, otherwise adds at the end.</param>
        public virtual void AttachAnnotation(Annotation annotation, bool atFront)
        {
            // Clear any existing parent object, so if we're moving the annotation it won't get unnecessarily cloned
            annotation.Parent = null;
            if (atFront)
                CreateAnnotations().Insert(0, annotation);
            else
                CreateAnnotations().Add(annotation);

            // Adjust the newlines to correct the formatting if not EOL, Postfix, a Message, or the left side of
            // a binary operator (because then it's a prefix operator, with special newline formatting).
            if (!annotation.IsEOL && !annotation.IsInfix && !annotation.IsPostfix && !(annotation is Message)
                && !(_parent is BinaryOperator && ((BinaryOperator)_parent).Left == this))
            {
                // First, upgrade the object to IsFirstOnLine if it's not an Expression, and its newlines haven't
                // been manually set yet, and the default newline state was changed by the added annotation.
                if (!(this is Expression) && !IsNewLinesSet && !IsFirstOnLine && IsFirstOnLineDefault)
                {
                    // Use IsFirstOnLine instead of SetNewLines(), and force IsNewLinesSet to false, so that this
                    // works properly for Blocks.
                    IsFirstOnLine = true;
                    IsNewLinesSet = false;
                }

                // If the annotation was prefixed to any existing annotations (or if there weren't any
                // existing relevant annotations), then swap newlines with the parent object.
                CodeObject swapWith = this;

                // If the annotation was added after any existing annotations, swap newline counts with the
                // last existing annotation that isn't a Message or EOL comment (since they don't affect newlines).
                if (!atFront && _annotations.Count > 1)
                {
                    for (int i = _annotations.Count - 2; i >= 0; --i)
                    {
                        Annotation existing = _annotations[i];
                        if (!(existing is Message) && !existing.IsEOL)
                        {
                            swapWith = existing;
                            break;
                        }
                    }
                }

                // Swap the newline counts, but only if the annotation has more than the current object, or
                // if we added to the front, then also swap if they're different.
                if (annotation.NewLines > swapWith.NewLines || (atFront && annotation.NewLines != swapWith.NewLines))
                {
                    int newLines = swapWith.NewLines;
                    swapWith.SetNewLines(annotation.NewLines);
                    annotation.SetNewLines(newLines);
                }
            }

            // Send notification if the annotation is 'listed'
            if (annotation.IsListed)
                NotifyListedAnnotationAdded(annotation);
        }

        /// <summary>
        /// Attach an <see cref="Annotation"/> (<see cref="Comment"/>, <see cref="DocComment"/>, <see cref="Attribute"/>, <see cref="CompilerDirective"/>, or <see cref="Message"/>) to the <see cref="CodeObject"/>.
        /// </summary>
        /// <param name="annotation">The <see cref="Annotation"/>.</param>
        public void AttachAnnotation(Annotation annotation)
        {
            AttachAnnotation(annotation, false);
        }

        /// <summary>
        /// Move any annotations from the specified location to the specified destination location.
        /// </summary>
        public void MoveAnnotations(AnnotationFlags fromFlag, AnnotationFlags toFlag)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation.AnnotationFlags.HasFlag(fromFlag))
                        annotation.AnnotationFlags = (annotation.AnnotationFlags & ~fromFlag) | toFlag;
                }
            }
        }

        /// <summary>
        /// Returns the <see cref="DocSummary"/> documentation comment, or null if none exists.
        /// </summary>
        public virtual DocSummary GetDocSummary()
        {
            return (_annotations != null ? Enumerable.FirstOrDefault(Enumerable.Select<DocComment, DocSummary>(Enumerable.OfType<DocComment>(_annotations),
                delegate(DocComment annotation) { return annotation.GetDocSummary(); })) : null);
        }

        /// <summary>
        /// Get the comment that satisfies the specified predicate.
        /// </summary>
        public Comment GetComment(Predicate<Comment> predicate)
        {
            if (_annotations != null)
                return (Comment)Enumerable.FirstOrDefault(_annotations, delegate(Annotation annotation) { return annotation is Comment && predicate((Comment)annotation); });
            return null;
        }

        /// <summary>
        /// Remove all messages from this object, or optionally only from the specified source.
        /// </summary>
        /// <param name="messageSource">The message source.</param>
        /// <param name="notify">Pass <c>false</c> to NOT send notifications.</param>
        public void RemoveAllMessages(MessageSource messageSource, bool notify)
        {
            RemoveAllAnnotationsWhere<Message>(delegate(Message annotation) { return annotation.Source == messageSource || messageSource == MessageSource.Unspecified; }, notify);
        }

        /// <summary>
        /// Remove all messages from this object, or optionally only from the specified source.
        /// </summary>
        /// <param name="messageSource">The message source.</param>
        public void RemoveAllMessages(MessageSource messageSource)
        {
            RemoveAllMessages(messageSource, true);
        }

        /// <summary>
        /// Remove all messages from this object, or optionally only from the specified source.
        /// </summary>
        public void RemoveAllMessages()
        {
            RemoveAllMessages(MessageSource.Unspecified, true);
        }

        /// <summary>
        /// Remove all annotations from this object where the specified predicate is true.
        /// </summary>
        public void RemoveAllAnnotationsWhere<T>(Predicate<T> predicate, bool notify) where T : Annotation
        {
            if (_annotations != null)
            {
                for (int i = _annotations.Count - 1; i >= 0; --i)
                {
                    T annotation = _annotations[i] as T;
                    if (annotation != null && predicate(annotation))
                    {
                        _annotations.RemoveAt(i);
                        if (annotation.IsListed && notify)
                            NotifyListedAnnotationRemoved(annotation);
                    }
                }
                if (_annotations.Count == 0)
                    _annotations = null;
            }
        }

        /// <summary>
        /// Remove all annotations from this object where the specified predicate is true.
        /// </summary>
        public void RemoveAllAnnotationsWhere<T>(Predicate<T> predicate) where T : Annotation
        {
            RemoveAllAnnotationsWhere(predicate, true);
        }

        /// <summary>
        /// Propagate listed annotations to the higher levels.
        /// </summary>
        protected virtual void NotifyListedAnnotationAdded(Annotation annotation)
        {
            if (_parent != null)
                _parent.NotifyListedAnnotationAdded(annotation);
        }

        /// <summary>
        /// Remove listed annotations from the higher levels.
        /// </summary>
        protected virtual void NotifyListedAnnotationRemoved(Annotation annotation)
        {
            if (_parent != null)
                _parent.NotifyListedAnnotationRemoved(annotation);
        }

        /// <summary>
        /// Returns <c>true</c> if the attribute with the specified name exists on the object, otherwise <c>false</c>.
        /// </summary>
        public bool HasAttribute(string attributeName)
        {
            return (_annotations != null && Enumerable.Any(_annotations,
                delegate(Annotation annotation) { return annotation is Attribute && ((Attribute)annotation).FindAttributeExpression(attributeName) != null; }));
        }

        /// <summary>
        /// Returns the first attribute expression (<see cref="Call"/> or <see cref="ConstructorRef"/>) with the specified name on the <see cref="CodeObject"/>.
        /// </summary>
        public Expression GetAttribute(string attributeName)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is Attribute)
                    {
                        Expression expression = ((Attribute)annotation).FindAttributeExpression(attributeName);
                        if (expression != null)
                            return expression;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Remove the attribute expression with the specified name.
        /// </summary>
        /// <returns><c>true</c> if found and removed, otherwise <c>false</c>.</returns>
        public bool RemoveAttribute(string attributeName)
        {
            if (_annotations != null)
            {
                for (int i = _annotations.Count - 1; i >= 0; --i)
                {
                    if (_annotations[i] is Attribute)
                    {
                        Attribute attribute = (Attribute)_annotations[i];
                        if (attribute.RemoveAttributeExpression(attributeName))
                        {
                            // If the attribute has no more expressions, remove it
                            if (!attribute.HasAttributeExpressions)
                                _annotations.RemoveAt(i);
                            return true;
                        }
                    }
                }
                if (_annotations.Count == 0)
                    _annotations = null;
            }
            return false;
        }

        /// <summary>
        /// Get the type of the worst attached message.
        /// </summary>
        public MessageSeverity GetWorstMessageType()
        {
            MessageSeverity type = MessageSeverity.Unspecified;
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is Message)
                    {
                        Message message = (Message)annotation;
                        if (type == MessageSeverity.Unspecified && message.Severity != MessageSeverity.Unspecified)
                            type = message.Severity;
                        else if (message.Severity != MessageSeverity.Unspecified && message.Severity < type)
                            type = message.Severity;
                    }
                }
            }
            return type;
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// Parse a code object.
        /// </summary>
        protected CodeObject(Parser parser, CodeObject parent)
        {
            _parent = parent;
            Token token = parser.Token;
            if (token != null)
            {
                NewLines = token.NewLines;
                SetLineCol(token);

                // Remove more than 3 consecutive blank lines if auto-cleanup is on
                if (AutomaticFormattingCleanup && !parser.IsGenerated && NewLines > 4)
                    NewLines = 4;
            }
        }

        /// <summary>
        /// Parse the specified expected token, attaching a parse error to the current object if it doesn't exist.
        /// </summary>
        /// <returns>True if the token was successfully parse, otherwise false.</returns>
        protected internal bool ParseExpectedToken(Parser parser, string token)
        {
            if (parser.TokenText == token)
            {
                parser.NextToken();  // Move past expected token
                return true;
            }
            parser.AttachMessage(this, "'" + token + "' expected", parser.Token);
            return false;
        }

        /// <summary>
        /// Determine if the specified comment should be associated with the current code object during parsing.
        /// </summary>
        public virtual bool AssociateCommentWhenParsing(CommentBase comment)
        {
            return false;
        }

        private void MoveComment(CommentBase comment, bool atFront, bool forceNewLine, AnnotationFlags annotationFlags)
        {
            if ((forceNewLine || comment.IsEOL) && !comment.IsFirstOnLine)
                comment.IsFirstOnLine = true;
            comment.IsEOL = false;
            comment.AnnotationFlags = comment.AnnotationFlags | annotationFlags;
            AttachAnnotation(comment, atFront);
            AdjustCommentIndentation(comment);
        }

        /// <summary>
        /// Move all (regular or EOL) comments from the specified token to the current code object, converting any
        /// EOL comments to regular comments (which will be rendered inline if necessary).
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="atFront">Inserts at the front of any existing annotations if true, otherwise adds at the end.</param>
        /// <param name="forceNewLine">Force all comments to start on a new line if true.</param>
        /// <param name="annotationFlags">Annotation flags to set on the comments.</param>
        public void MoveAllComments(Token token, bool atFront, bool forceNewLine, AnnotationFlags annotationFlags)
        {
            if (token != null && token.TrailingComments != null)
            {
                // If we're inserting, we also need to traverse the list in reverse
                if (atFront)
                {
                    for (int i = token.TrailingComments.Count - 1; i >= 0; --i)
                        MoveComment(token.TrailingComments[i], true, forceNewLine, annotationFlags);
                }
                else
                {
                    foreach (CommentBase comment in token.TrailingComments)
                        MoveComment(comment, false, forceNewLine, annotationFlags);
                }
                token.TrailingComments = null;
            }
        }

        /// <summary>
        /// Move all (regular or EOL) comments from the specified token to the current code object, converting any
        /// EOL comments to regular comments (which will be rendered inline if necessary).
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="atFront">Inserts at the front of any existing annotations if true, otherwise adds at the end.</param>
        /// <param name="forceNewLine">Force all comments to start on a new line if true.</param>
        public void MoveAllComments(Token token, bool atFront, bool forceNewLine)
        {
            MoveAllComments(token, atFront, forceNewLine, AnnotationFlags.None);
        }

        /// <summary>
        /// Move all (regular or EOL) comments from the specified token to the current code object, converting any
        /// EOL comments to regular comments (which will be rendered inline if necessary).
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="atFront">Inserts at the front of any existing annotations if true, otherwise adds at the end.</param>
        public void MoveAllComments(Token token, bool atFront)
        {
            MoveAllComments(token, atFront, false, AnnotationFlags.None);
        }

        /// <summary>
        /// Move all (regular or EOL) comments from the specified token to the current code object, converting any
        /// EOL comments to regular comments (which will be rendered inline if necessary).
        /// </summary>
        /// <param name="token">The token.</param>
        public void MoveAllComments(Token token)
        {
            MoveAllComments(token, false, false, AnnotationFlags.None);
        }

        private void MoveComment(CommentBase comment, bool atFront, List<CommentBase> comments, int i)
        {
            AttachAnnotation(comment, atFront);
            AdjustCommentIndentation(comment);
            comments.RemoveAt(i);
        }

        /// <summary>
        /// Move any non-EOL comments from the specified token to the current code object.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="atFront">Inserts at the front of any existing annotations if true, otherwise adds at the end.</param>
        public void MoveComments(Token token, bool atFront)
        {
            if (token != null)
            {
                List<CommentBase> comments = token.TrailingComments;
                if (comments != null)
                {
                    // If we're inserting, we also need to traverse the list in reverse
                    if (atFront)
                    {
                        for (int i = comments.Count - 1; i >= 0; --i)
                        {
                            CommentBase comment = comments[i];
                            if (!comment.IsEOL)
                                MoveComment(comment, true, comments, i);
                        }
                    }
                    else
                    {
                        for (int i = 0; i < comments.Count; )
                        {
                            CommentBase comment = comments[i];
                            if (!comment.IsEOL)
                            {
                                MoveComment(comment, false, comments, i);
                                continue;
                            }
                            // Only increment if we didn't change the list!
                            ++i;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Move any non-EOL comments from the specified token to the current code object.
        /// </summary>
        /// <param name="token">The token.</param>
        public void MoveComments(Token token)
        {
            MoveComments(token, false);
        }

        /// <summary>
        /// Move any non-EOL comments from the specified token to the current code object as Post comments.
        /// </summary>
        /// <param name="token">The token.</param>
        public void MoveCommentsAsPost(Token token)
        {
            if (token != null)
            {
                List<CommentBase> comments = token.TrailingComments;
                if (comments != null && comments.Count > 0)
                {
                    for (int i = 0; i < comments.Count; )
                    {
                        CommentBase comment = comments[i];
                        if (!comment.IsEOL)
                        {
                            comment.IsPostfix = true;
                            MoveComment(comment, false, comments, i);
                            continue;
                        }
                        // Only increment if we didn't change the list!
                        ++i;
                    }
                }
            }
        }

        /// <summary>
        /// Move any EOL comment from the specified token to the current code object.
        /// </summary>
        /// <param name="token">The token.</param>
        /// <param name="includeInlines">Include inline comments as EOL comments if true.</param>
        /// <param name="atFront">Inserts at the front of any existing annotations if true, otherwise adds at the end.</param>
        public CommentBase MoveEOLComment(Token token, bool includeInlines, bool atFront)
        {
            if (token != null)
            {
                List<CommentBase> comments = token.TrailingComments;
                if (comments != null && comments.Count > 0)
                {
                    // Move any EOL comment (a Token can only have one, and it should be the first one).
                    CommentBase comment = comments[0];
                    if (comment.IsEOL)
                    {
                        AttachAnnotation(comment, atFront);
                        //AdjustCommentIndentation(comment);  // Shouldn't need to do this for EOL comments
                        comments.RemoveAt(0);
                        return comment;
                    }
                    // Also treat any inline comment as an EOL comment if so directed - this allows inline comments embedded
                    // within expressions to be treated as EOL comments on the preceeding sub-expression.  In some cases, we
                    // can't do this, such as with EOL comments following a comma that are being moved to the preceeding expression
                    // (in such a case, they only belong to the preceeding expression if they're true EOL comments, otherwise they
                    // should be associated with the following expression).
                    if (includeInlines && !comment.IsFirstOnLine && !(comment is DocComment))
                    {
                        comment.IsEOL = true;
                        AttachAnnotation(comment, atFront);
                        comments.RemoveAt(0);
                        return comment;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Move any EOL comment from the specified token to the current code object.
        /// </summary>
        /// <param name="token">The token.</param>
        public CommentBase MoveEOLComment(Token token)
        {
            return MoveEOLComment(token, true, false);
        }

        /// <summary>
        /// Move any EOL comment from the specified token to the current code object as an Infix EOL comment.
        /// </summary>
        /// <param name="token">The token.</param>
        public void MoveEOLCommentAsInfix(Token token)
        {
            CommentBase comment = MoveEOLComment(token, true, false);
            if (comment != null)
                comment.IsInfix = true;
        }

        private void MoveAnnotation(ChildList<Annotation> annotations, int i, Annotation annotation)
        {
            annotations.RemoveAt(i);
            if (annotation.IsListed)
                NotifyListedAnnotationRemoved(annotation);
            AttachAnnotation(annotation);
        }

        /// <summary>
        /// Move any EOL comment from the specified code object to the current code object.
        /// </summary>
        public void MoveEOLComment(CodeObject obj)
        {
            ChildList<Annotation> annotations = obj.Annotations;
            if (annotations != null)
            {
                for (int i = 0; i < annotations.Count; ++i)
                {
                    if (annotations[i] is Comment)
                    {
                        // Move the first EOL comment
                        Comment comment = (Comment)annotations[i];
                        if (comment.IsEOL)
                        {
                            comment.IsInfix = false;
                            MoveAnnotation(annotations, i, comment);
                            break;
                        }
                    }
                }
                if (annotations.Count == 0)
                    obj._annotations = null;
            }
        }

        /// <summary>
        /// Move any EOL or Postfix annotations from the specified code object to the current code object.
        /// </summary>
        public void MoveEOLAndPostAnnotations(CodeObject obj)
        {
            ChildList<Annotation> annotations = obj.Annotations;
            if (annotations != null)
            {
                for (int i = 0; i < annotations.Count; )
                {
                    Annotation annotation = annotations[i];
                    if (((annotation is Comment && annotation.IsEOL) || annotation.IsPostfix) && !annotation.IsInfix)
                    {
                        MoveAnnotation(annotations, i, annotation);
                        continue;
                    }
                    // Only increment if we didn't change the list!
                    ++i;
                }
                if (annotations.Count == 0)
                    obj._annotations = null;
            }
        }

        /// <summary>
        /// Move any prefix annotations on the specified code object to the current code object as post annotations.
        /// </summary>
        public void MovePrefixAnnotationsAsPost(CodeObject obj)
        {
            ChildList<Annotation> annotations = obj.Annotations;
            if (annotations != null)
            {
                for (int i = 0; i < annotations.Count; )
                {
                    Annotation annotation = annotations[i];
                    if (!annotation.IsEOL && !annotation.IsPostfix && !annotation.IsInfix)
                    {
                        annotation.IsPostfix = true;
                        MoveAnnotation(annotations, i, annotation);
                        continue;
                    }
                    // Only increment if we didn't change the list!
                    ++i;
                }
                if (annotations.Count == 0)
                    obj._annotations = null;
            }
        }

        /// <summary>
        /// Adjust the content of the specified comment to compensate if it was outdented.
        /// </summary>
        public static void AdjustCommentIndentation(CommentBase commentBase)
        {
            // If the comment was "outdented", remove spaces from the left of the comment text to
            // compensate for the indentation level.
            if (commentBase is Comment)
            {
                Comment comment = (Comment)commentBase;
                int removeSpaceCount = comment.GetIndentSpaceCount() - comment.PrefixSpaceCount - (comment.IsBlock ? 0 : CodeDOM.Comment.ParseToken.Length);
                if (removeSpaceCount > 0)
                {
                    // If we fail to remove the desired count of spaces, and 1 space is implied, then
                    // try removing one less space, and if that works set the NoSpaceAfterDelimiter flag.
                    if (!comment.RemoveSpaces(removeSpaceCount) && !comment.NoSpaceAfterDelimiter)
                    {
                        if (comment.RemoveSpaces(removeSpaceCount - 1))
                            comment.NoSpaceAfterDelimiter = true;
                    }
                }
            }
        }

        /// <summary>
        /// Parse any comments, attributes, compiler directives.
        /// </summary>
        protected void ParseAnnotations(Parser parser, CodeObject parent, bool forcePostfix, bool forceNotPostfix)
        {
            bool isPostfix = !forceNotPostfix;

            // Look only for comments, doc comments, attributes, compiler directives, or trailing regular comments on the last token
            while (parser.TokenType == TokenType.Comment || parser.TokenType == TokenType.DocCommentStart
                || parser.TokenText == Attribute.ParseTokenStart || parser.TokenText == CompilerDirective.ParseToken || parser.LastToken.HasTrailingComments)
            {
                // Consume any regular comments on the last token first, otherwise get the next token
                CodeObject obj;
                if (parser.LastToken.HasTrailingComments)
                {
                    obj = parser.LastToken.TrailingComments[0];
                    parser.LastToken.TrailingComments.RemoveAt(0);
                }
                else
                    obj = parser.ProcessToken(parent);

                // If we're not forcing post-mode, exit post-mode if we hit anything that isn't a non-starting conditional directive
                if (isPostfix && !forcePostfix && !(obj is ConditionalDirectiveBase && !(obj is IfDirective)))
                    isPostfix = false;

                if (obj != null)
                {
                    // Process the object - attach if post, otherwise add to unused list
                    if (isPostfix)
                    {
                        if (obj is Comment || obj is CompilerDirective)
                            AttachAnnotation((Annotation)obj, AnnotationFlags.IsPostfix);
                        else
                            parser.AddUnused(obj);
                    }
                    else
                        parser.AddUnused(obj);
                }
            }
        }

        /// <summary>
        /// Parse annotations from the Unused list.
        /// </summary>
        protected internal void ParseUnusedAnnotations(Parser parser, CodeObject parent, bool includeAll, int ifCount)
        {
            // Parse any preceeding documentation comments, attributes, and (only if 'includeAll' is true) compiler
            // directives from the unused list.  Also, always parse the special-case of an attribute sandwiched by
            // compiler directives.
            bool isFirst = true;
            bool stopOnDirective = false;
            Annotation lastUnusedAnnotation = parser.LastUnusedCodeObject as Annotation;
            while (lastUnusedAnnotation != null)
            {
                if (stopOnDirective && lastUnusedAnnotation is CompilerDirective)
                    break;

                // If 'includeAll' isn't true, stop under certain conditions
                if (!includeAll)
                {
                    // Stop if we hit a compiler directive, except for special cases
                    if (lastUnusedAnnotation is CompilerDirective)
                    {
                        // If a conditional directive other than '#if' (and not the first annotation and separated by a blank line
                        // from the current object) is preceeded only by attributes and/or other conditional directives, then allow it.
                        if (lastUnusedAnnotation is ConditionalDirectiveBase && !(lastUnusedAnnotation is IfDirective)
                            && !(isFirst && NewLines > 1))
                        {
                            // Verify that we have only attributes, doc comments, and/or conditional directives back to a starting '#if'
                            bool verified = false;
                            for (int unusedIndex = parser.Unused.Count - 2; unusedIndex >= 0; --unusedIndex)
                            {
                                CodeObject previousObject = parser.GetUnusedCodeObject(unusedIndex);
                                if (previousObject is IfDirective)
                                {
                                    verified = true;
                                    break;
                                }
                                if (!(previousObject is Attribute || previousObject is DocComment || previousObject is ConditionalDirective))
                                    break;
                            }
                            // Abort if we didn't find a starting '#if'
                            if (!verified)
                                break;

                            // Turn on inclusion of compiler attributes so we'll read them all up to the starting '#if'
                            includeAll = true;
                            ++ifCount;
                        }
                        else
                            break;
                    }

                    // Stop if we hit a regular comment as the first annotation (since we have no way of being sure that it
                    // belongs to the current object as opposed to a block of code), EXCEPT when it's preceeded by a doc comment.
                    if (lastUnusedAnnotation is Comment && isFirst)
                    {
                        CodeObject nextToLastUnused = parser.GetUnusedCodeObject(parser.Unused.Count - 2);
                        if (!(nextToLastUnused is DocComment))
                            break;
                    }

                    // Stop if we hit a global attribute (assembly or module)
                    if (lastUnusedAnnotation is Attribute && ((Attribute)lastUnusedAnnotation).IsGlobal)
                        break;
                }

                UnusedCodeObject unused = (UnusedCodeObject)parser.RemoveLastUnused();
                MoveComments(unused.LastToken, true);
                Annotation annotation = (Annotation)unused.CodeObject;
                bool preceedingBlankLine = (annotation.NewLines > 1);
                AttachAnnotation(annotation, true);
                isFirst = false;

                // If 'includeAll' is true (which is used for expressions, and for block statements when it's been determined
                // that they're "sandwiched" by conditional directives), stop if any specified 'ifCount' is reached.
                if (includeAll)
                {
                    if (annotation is IfDirective && ifCount > 0 && --ifCount == 0)
                    {
                        // We're done with preceeding conditional directives, but also still check for preceeding attributes
                        // and/or doc comments.
                        stopOnDirective = true;
                    }
                }
                // Otherwise, stop if we find blank lines between any of the annotations
                else if (preceedingBlankLine)
                    break;

                lastUnusedAnnotation = parser.LastUnusedCodeObject as Annotation;
            }
        }

        /// <summary>
        /// Parse annotations from the Unused list.
        /// </summary>
        protected internal void ParseUnusedAnnotations(Parser parser, CodeObject parent, bool includeAll)
        {
            ParseUnusedAnnotations(parser, parent, includeAll, 0);
        }

        #endregion

        #region /* RESOLVING */

        /// <summary>
        /// Resolve all child symbolic references, using the specified <see cref="ResolveCategory"/> and <see cref="ResolveFlags"/>.
        /// </summary>
        /// <remarks>
        /// Resolves the current object if it's an <see cref="UnresolvedRef"/>, otherwise recursively resolves all child UnresolvedRefs.
        /// Leaves already-resolved references alone - unless the Unresolve flag is specified, in which case it converts
        /// all resolved references back to UnresolvedRefs while leaving existing UnresolvedRefs alone.
        /// 
        /// Note that all declarations are processed during parsing - only symbolic references to declarations
        /// must be resolved.  References to built-in types (types with keywords) and references to namespaces
        /// in NamespaceDecl statements are resolved during parsing (and all Namespace objects are declared).
        /// 
        /// If this method is called on a Solution, Project, or CodeUnit, then resolving will proceed in 3 phases
        /// in order to resolve all references in a single "pass" without dependency problems:
        ///    - Phase 1 resolves top-level statements in CodeUnits, NamespaceDecls, and then stops at TypeDecls
        ///      after resolving any base lists (necessary so that base types will be resolved for later phases).
        ///    - Phase 2 stops at the bodies of methods/properties, or base/this initializers, or field initializers
        ///      (this gets all 'signatures' of members resolved for the final phase).
        ///    - Phase 3 continues resolving method bodies and the other items not done in phase 2.
        /// These 3 phases neatly resolves all references in a single attempt without dependency issues - with a
        /// few minor exceptions.  Switches resolve all Case constant expressions before the Case bodies in order to
        /// handle any forward references by 'goto case ...' statements.  Types of fields initialized to constant
        /// values via references to other constant fields might not evaluate to their final constant value during
        /// Phase 3, but they'll have their correct type and that should be good enough.
        /// 
        /// Currently, all 3 phases traverse the tree from the top down as opposed to building lists of objects at
        /// which to continue for the later 2 phases - such lists could be huge, and the first 2 phases only require
        /// traversal of the upper 3 to 4 levels of the tree for most references.
        /// 
        /// Symbols might be resolved:
        ///    - For an entire file, after the file is added and all parsing is complete for all added files.
        ///      After loading and parsing a solution, after adding an existing project to a solution and parsing it,
        ///      or after adding an existing file to a project and parsing it.  After all parsing is complete, *all*
        ///      references (resolved *and* unresolved) must be resolved.  Resolved symbols must be re-resolved because
        ///      new types or partial class members could cause conflicts.  Ideally, resolution would be limited to
        ///      references that might need it, but to be safe we should just re-resolve all references.
        ///    - For an entire file, if a 'using' statement is added or removed.
        ///      All references (resolved or unresolved) must be re-resolved to determine if they are still resolved
        ///      or now have conflicts.  HOWEVER, if conflicts arise in such a case, they should probably be auto-
        ///      matically resolved by default, by adding the use of namespace prefixes to keep references tied to
        ///      the same objects to which they were already resolved.
        ///    - For all files with a 'using' or 'namespace' if the associated namespace has had a type added,
        ///      removed, or renamed; or if a public member of a type was added, removed, renamed, or its signature
        ///      changed.  Of course, this applies to all files when types in the global namespace are changed.  In
        ///      theory, scope could be taken into consideration in order to avoid resolving all references in the
        ///      entire program.  For renames, resolving is necessary only to detect conflicts.
        /// </remarks>
        public virtual CodeObject Resolve(ResolveCategory resolveCategory, ResolveFlags flags)
        {
            return this;
        }

        /// <summary>
        /// Resolve all child symbolic references.
        /// </summary>
        public virtual void Resolve(ResolveFlags flags)
        {
            Resolve(ResolveCategory.CodeObject, ResolveFlags.None);
        }

        /// <summary>
        /// Resolve all child symbolic references.
        /// </summary>
        public void Resolve()
        {
            Resolve(ResolveFlags.None);
        }

        /// <summary>
        /// Resolve any attached attributes.
        /// </summary>
        public void ResolveAttributes(ResolveFlags flags)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is Attribute)
                        annotation.Resolve(ResolveCategory.CodeObject, flags);
                }
            }
        }

        /// <summary>
        /// Resolve any references in attached documentation comments.
        /// </summary>
        public void ResolveDocComments(ResolveFlags flags)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is DocComment)
                        annotation.Resolve(ResolveCategory.CodeObject, flags | ResolveFlags.InDocComment);
                }
            }
        }

        /// <summary>
        /// Resolve child code objects that match the specified name, moving up the tree until a complete match is found.
        /// </summary>
        /// <param name="name">The name of the reference (might be modified from the one inside the <see cref="UnresolvedRef"/> in special cases).</param>
        /// <param name="resolver">The <see cref="Resolver"/> object, which contains the <see cref="UnresolvedRef"/> being resolved.</param>
        /// <remarks>
        /// The <see cref="UnresolvedRef"/> object will be updated to reflect the results of the search.
        /// Search for objects by name recursively up the parent code tree, finding:
        ///   - LocalDecls in the current block (must be verified later during analysis to be defined *before* a reference).
        ///   - LocalDecls in each parent block (must be verified later during analysis to be defined *before* a reference) up to
        ///     the parent MethodDeclBase level, including LocalDecls of Catch, Using, For, ForEach statements.
        ///   - ParameterDecls of the parent MethodDeclBase (MethodDecl, GenericMethodDecl, ConstructorDecl, AccessorDecl)
        ///     or AnonymousMethod, and TypeParameters if it's a GenericMethodDecl.
        ///   - Members (fields, properties, methods, etc) of the current TypeDecl, including base types and interfaces,
        ///     and TypeParameters (if any).
        ///   - Namespaces or TypeDecls of the current NamespaceDecl, and of any of its UsingDirectives.
        /// 
        /// Not here, but during the analysis phase:
        ///   - Namespaces or TypeDecls of any other known Namespaces (give an option to add a 'using').
        /// </remarks>
        public virtual void ResolveRefUp(string name, Resolver resolver)
        {
            if (_parent != null)
                _parent.ResolveRefUp(name, resolver);
        }

        /// <summary>
        /// Similar to <see cref="ResolveRefUp"/>, but skips trying to resolve the symbol in the body or parameters of a
        /// method (used for resolving parameter types).
        /// </summary>
        public virtual void ResolveRefUpSkipMethodBody(string name, Resolver resolver)
        {
            // By default, do the same behavior as ResolveRefUp()
            ResolveRefUp(name, resolver);
        }

        /// <summary>
        /// Resolve child code objects that match the specified name are valid goto targets, moving up the tree until a complete match is found.
        /// </summary>
        /// <param name="name">The name of the desired goto target(s).</param>
        /// <param name="resolver">The resolver object.</param>
        /// <remarks>
        /// Search for objects by name recursively up the parent code tree, finding:
        ///   - Labels and SwitchItems in the current block.
        ///   - Labels and SwitchItems in each parent block up to the parent MethodDeclBase level.
        /// </remarks>
        public virtual void ResolveGotoTargetUp(string name, Resolver resolver)
        {
            if (_parent != null)
                _parent.ResolveGotoTargetUp(name, resolver);
        }

        /// <summary>
        /// Returns true if the code object is an <see cref="UnresolvedRef"/> or has any <see cref="UnresolvedRef"/> children.
        /// </summary>
        public virtual bool HasUnresolvedRef()
        {
            return false;
        }

        /// <summary>
        /// Evaluate the type or namespace associated with the code object.
        /// </summary>
        /// <returns>The resulting TypeRef, UnresolvedRef, or NamespaceRef.</returns>
        public virtual SymbolicRef EvaluateTypeOrNamespace(bool withoutConstants)
        {
            // By default, return null, meaning that evaluating the type of the code object doesn't apply.
            // Most statements will evaluate to null, except for some such as Return and YieldReturn, which
            // will evaluate to the type of their returned expression.
            // All Expressions will evaluate to some type (or namespace), even if it's just 'object'.
            return null;
        }

        /// <summary>
        /// Evaluate the type or namespace associated with the code object.
        /// </summary>
        /// <returns>The resulting TypeRef, UnresolvedRef, or NamespaceRef.</returns>
        public SymbolicRef EvaluateTypeOrNamespace()
        {
            return EvaluateTypeOrNamespace(false);
        }

        #endregion

        #region /* ANALYSIS */

        /// <summary>
        /// Accept the specified visitor object for the current code object and it's children.
        /// </summary>
        public abstract void Accept(IVisitor visitor);

        /// <summary>
        /// Accept the specified visitor object for all regular (non-EOL, non-Infix, non-Postfix) annotations (comments, attributes, compiler directives).
        /// </summary>
        public void AcceptAnnotations(IVisitor visitor)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (!annotation.IsEOL && !annotation.IsInfix && !annotation.IsPostfix)
                        annotation.Accept(visitor);
                }
            }
        }

        /// <summary>
        /// Accept the specified visitor object for all EOL comments.
        /// </summary>
        public void AcceptEOLComments(IVisitor visitor)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation.IsEOL && !annotation.IsInfix)
                        annotation.Accept(visitor);
                }
            }
        }

        /// <summary>
        /// Accept the specified visitor object for all Infix EOL comments.
        /// </summary>
        public void AcceptInfixEOLComments(IVisitor visitor)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is Comment && annotation.IsEOL && annotation.IsInfix)
                        annotation.Accept(visitor);
                }
            }
        }

        /// <summary>
        /// Accept the specified visitor object for all Infix comments.
        /// </summary>
        public void AcceptInfixComments(IVisitor visitor, AnnotationFlags infixMask)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is Comment)
                    {
                        if (infixMask != 0 ? (annotation.AnnotationFlags & AnnotationFlags.InfixMask) == infixMask : annotation.IsInfix)
                            annotation.Accept(visitor);
                    }
                }
            }
        }

        /// <summary>
        /// Accept the specified visitor object for all specified Infix or Postfix annotations (comments, compiler directives).
        /// </summary>
        public void AcceptAnnotations(IVisitor visitor, AnnotationFlags positionFlag)
        {
            if (_annotations != null)
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation.AnnotationFlags.HasFlag(positionFlag))
                        annotation.Accept(visitor);
                }
            }
        }

        /// <summary>
        /// Calculate metrics for this code object and its children.
        /// </summary>
        /// <remarks>
        /// Metrics do not include "compiler-generated" code objects, such as:
        ///    - Hidden default constructors for classes with no constructors declared
        ///    - Hidden bodies for delegates, with a constructor and Begin/EndInvoke methods
        ///    - A hidden NamespaceRef to the 'global' namespace for each CodeUnit
        ///    - A hidden ExternAlias to the 'global' namespace for resolving global-scope references via the Lookup operator
        /// Generated source files are included by default when metrics are calculated for a project or solution,
        /// such as files generated from XAML.
        /// </remarks>
        public Metrics CalculateMetrics()
        {
            MetricsVisitor metricsVisitor = new MetricsVisitor(this);
            try
            {
                return metricsVisitor.CalculateMetrics();
            }
            catch (Exception ex)
            {
                Log.Exception(ex, "calculating metrics");
            }
            return null;
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// True if the code object only requires a single line for display by default.
        /// </summary>
        public virtual bool IsSingleLineDefault
        {
            get { return !HasFirstOnLineAnnotations; }
        }

        /// <summary>
        /// Determines if the code object only requires a single line for display.
        /// </summary>
        public virtual bool IsSingleLine
        {
            get
            {
                if (_annotations != null && _annotations.Count > 0)
                {
                    foreach (Annotation annotation in _annotations)
                    {
                        if (!(annotation is Message))
                        {
                            if (annotation.IsFirstOnLine || !annotation.IsSingleLine)
                                return false;
                        }
                    }
                }
                return true;
            }
            set
            {
                if (_annotations != null && _annotations.Count > 0)
                {
                    foreach (Annotation annotation in _annotations)
                    {
                        if (!((annotation is Comment && annotation.IsEOL) || annotation is Message || annotation is CompilerDirective))
                        {
                            annotation.IsFirstOnLine = !value;
                            if (value)
                                annotation.IsSingleLine = true;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// True if the code object defaults to starting on a new line.
        /// </summary>
        public virtual bool IsFirstOnLineDefault
        {
            get { return true; }
        }

        /// <summary>
        /// Determine a default of 1 or 2 newlines when adding items to a <see cref="Block"/>.
        /// </summary>
        public virtual int DefaultNewLines(CodeObject previous)
        {
            // Default to a preceeding blank line if the object has first-on-line annotations
            return (HasFirstOnLineAnnotations ? 2 : 1);
        }

        /// <summary>
        /// Move formatting from the specified code object to the current object.
        /// </summary>
        public void MoveFormatting(CodeObject codeObject)
        {
            if (codeObject != null)
            {
                // Move any newlines to the object if there are more than it already has
                if (codeObject.NewLines > NewLines)
                    NewLines = codeObject.NewLines;

                // Clear the newlines on the object
                codeObject.NewLines = 0;
            }
        }

        /// <summary>
        /// Move formatting from the specified token to the current object.
        /// </summary>
        public void MoveFormatting(Token token)
        {
            if (token != null && token.NewLines > NewLines)
                NewLines = token.NewLines;
        }

        /// <summary>
        /// Copy formatting from another code object.
        /// </summary>
        public void CopyFormatting(CodeObject obj)
        {
            _formatFlags = (_formatFlags & FormatFlags.NonFormatting) | (obj._formatFlags & ~FormatFlags.NonFormatting);
        }

        /// <summary>
        /// Formatting flags.
        /// </summary>
        /// <remarks>
        /// Code objects contain some minimal formatting information used to control their "display" (whether as text
        /// or in a GUI), such as line breaks, parentheses, braces, and termination characters.
        /// When existing code is parsed, existing formatting is replicated as much as possible.  When code objects are
        /// manually created or modified, formatting is defaulted, but if certain settings are specifically set (such
        /// as newlines and parens/braces), then those settings must override the default formatting.  Some of these
        /// flags actually have nothing to do with formatting (such as Generated or Const), and are always preserved
        /// by the formatting logic.
        /// </remarks>
        [Flags]
        public enum FormatFlags : ushort
        {
            /// <summary>No formatting flags.</summary>
            None          = 0x0000,
            /// <summary>Newline count bit field mask (0 to 255 newlines preceed the object).</summary>
            NewLineMask   = 0x007f,
            /// <summary>Indicates that the newline count has been specifically set vs defaulted.</summary>
            NewLinesSet   = 0x0100,
            /// <summary>Used by Blocks/Initializers to represent a newline before the '{', as the NewLineMask area is used for newlines before the '}'.
            /// Used by objects with parens to represent a newline before the closing ')'.</summary>
            InfixNewLine  = 0x0200,
            /// <summary>Render the object with no indentation (for left-justified comments, initializers, anonymous methods, conditionals).</summary>
            NoIndentation = 0x0400,
            /// <summary>Render a terminator after the statement or expression.</summary>
            Terminator    = 0x0800,
            /// <summary>Render parens/braces (for Expressions/Blocks) around the code object.</summary>
            Grouping      = 0x1000,
            /// <summary>Indicates that the grouping flag has been specifically set vs defaulted.</summary>
            GroupingSet   = 0x2000,
            /// <summary>Used by TypeRef to indicate a reference to a constant value.</summary>
            Const         = 0x4000,
            /// <summary>Code object is "compiler" generated (used for default constructors, etc).</summary>
            Generated     = 0x8000,
            /// <summary>Flags that aren't formatting related.</summary>
            NonFormatting = (Const | Generated)
        }

        protected internal void SetFormatFlag(FormatFlags flag, bool value)
        {
            if (value)
                _formatFlags |= flag;
            else
                _formatFlags &= ~flag;
        }

        /// <summary>
        /// Default format the specified child field code object.
        /// </summary>
        protected virtual void DefaultFormatField(CodeObject field)
        {
            // Just default format the field by default: this method is overridden by derived classes that
            // want to override the default formatting of child fields, such as BinaryOperator forcing off
            // parens when consecutive operators are the same, or Statement forcing off parens for child
            // expressions.
            field.DefaultFormat();

            // Force off newlines for child fields by default (such as for LocalDecls in for/foreach, etc)
            if (!field._formatFlags.HasFlag(FormatFlags.NewLinesSet))
                field._formatFlags &= ~FormatFlags.NewLineMask;
        }

        /// <summary>
        /// Default format the code object.
        /// </summary>
        protected internal virtual void DefaultFormat()
        {
            // Clear the Terminator flag by default
            _formatFlags &= ~FormatFlags.Terminator;

            // Default the newlines if they haven't been explicitly set
            if (!_formatFlags.HasFlag(FormatFlags.NewLinesSet))
                _formatFlags = ((_formatFlags & ~(FormatFlags.NewLineMask | FormatFlags.InfixNewLine)) | (IsFirstOnLineDefault ? (FormatFlags)1 : 0));
        }

        /// <summary>
        /// Determines if the code object appears as the first item on a line.
        /// </summary>
        public virtual bool IsFirstOnLine
        {
            get { return ((_formatFlags & FormatFlags.NewLineMask) != 0); }
            set
            {
                if (value)
                {
                    // Do nothing if it's already been explicitly set to greater than 1, otherwise
                    // explicitly set it to 1.
                    if (NewLines <= 1)
                        NewLines = 1;
                }
                else
                    NewLines = 0;
            }
        }

        /// <summary>
        /// The number of newlines preceeding the object (0 to N).
        /// </summary>
        public virtual int NewLines
        {
            get { return (int)(_formatFlags & FormatFlags.NewLineMask); }
            set
            {
                SetNewLines(value);
                _formatFlags |= FormatFlags.NewLinesSet;
            }
        }

        /// <summary>
        /// Special method to set the newline count without setting the NewLinesSet flag.
        /// </summary>
        public void SetNewLines(int value)
        {
            // If we're changing to 0, also change all prefix comments to 0
            if (_annotations != null && value == 0 && ((_formatFlags & FormatFlags.NewLineMask) != 0))
                SetFirstOnLineForNonEOLComments(_annotations, false);

            // Change the newline value, truncating at the maximum allowed value
            if (value > (int)FormatFlags.NewLineMask)
                value = (int)FormatFlags.NewLineMask;
            _formatFlags = ((_formatFlags & ~FormatFlags.NewLineMask) | (FormatFlags)value);
        }

        /// <summary>
        /// Set the newline flag for all non-EOL comments in the collection.
        /// </summary>
        public static void SetFirstOnLineForNonEOLComments(ChildList<Annotation> annotations, bool value)
        {
            foreach (Annotation annotation in annotations)
            {
                if (annotation is CommentBase && !annotation.IsPostfix && !annotation.IsInfix)
                {
                    CommentBase comment = (CommentBase)annotation;
                    if (!comment.IsEOL)
                        comment.IsFirstOnLine = value;
                }
            }
        }

        /// <summary>
        /// Determines if the newline count has been set on the code object.
        /// </summary>
        public bool IsNewLinesSet
        {
            get { return _formatFlags.HasFlag(FormatFlags.NewLinesSet); }
            set { SetFormatFlag(FormatFlags.NewLinesSet, value); }
        }

        /// <summary>
        /// Determines if the code object has no indentation.
        /// </summary>
        public bool HasNoIndentation
        {
            get { return _formatFlags.HasFlag(FormatFlags.NoIndentation); }
            set { SetFormatFlag(FormatFlags.NoIndentation, value); }
        }

        /// <summary>
        /// Determines if the 'grouping' (has parens or braces) status has been set.
        /// </summary>
        public bool IsGroupingSet
        {
            get { return _formatFlags.HasFlag(FormatFlags.GroupingSet); }
        }

        /// <summary>
        /// Determines if the code object has a terminator character.
        /// </summary>
        public virtual bool HasTerminator
        {
            get { return _formatFlags.HasFlag(FormatFlags.Terminator); }
            set { SetFormatFlag(FormatFlags.Terminator, value); }
        }

        /// <summary>
        /// Determines if the code object is generated.
        /// </summary>
        public bool IsGenerated
        {
            get { return _formatFlags.HasFlag(FormatFlags.Generated); }
            set { SetFormatFlag(FormatFlags.Generated, value); }
        }

        #endregion

        #region /* RENDERING */

        /// <summary>
        /// Rendering behavior flags (passed through rendering methods).
        /// </summary>
        [Flags]
        public enum RenderFlags : uint
        {
            /// <summary>No flags set.</summary>
            None              = 0x00000000,
            /// <summary>Suppress indentation of the current block.</summary>
            NoBlockIndent     = 0x00000001,
            /// <summary>Suppress parens if empty (used by NewOperators).</summary>
            NoParensIfEmpty   = 0x00000002,
            /// <summary>Suppress rendering of EOL comments (used by WriteList).</summary>
            NoEOLComments     = 0x00000004,
            /// <summary>Suppress the next newline because it's already been pre-rendered.</summary>
            SuppressNewLine   = 0x00000008,
            /// <summary>Suppress type arguments when rendering a Type (used by TypeRef rendering).</summary>
            SuppressTypeArgs  = 0x00000010,
            /// <summary>Object needs a space prefix if it's not the first thing on the line.</summary>
            PrefixSpace       = 0x00000040,
            /// <summary>Object is a child prefix of another - the IsFirstOnLine flag actually means IsLastOnLine.</summary>
            IsPrefix          = 0x00000080,
            /// <summary>Suppress the space suffix on a prefix object.</summary>
            NoSpaceSuffix     = 0x00000100,
            /// <summary>Object is on the right side of a Dot operator.</summary>
            HasDotPrefix      = 0x00000200,
            /// <summary>Render as a declaration (might differ from references).</summary>
            Declaration       = 0x00001000,
            /// <summary>Rendering an attribute (hide "Attribute" suffix, hide parens if empty).</summary>
            Attribute         = 0x00002000,
            /// <summary>Increase the indentation level for any future newlines.</summary>
            IncreaseIndent    = 0x00004000,
            /// <summary>Render a terminator (after a statement or a ChildList).</summary>
            HasTerminator     = 0x00008000,
            /// <summary>Suppress rendering of separators (commas) between items in a ChildList.</summary>
            NoItemSeparators  = 0x00010000,
            /// <summary>Suppress rendering of post annoations (used by ChildList).</summary>
            NoPostAnnotations = 0x00020000,
            /// <summary>Suppress translations (used by DocText text rendering only).</summary>
            NoTranslations    = 0x00040000,
            /// <summary>Do NOT increase the indentation level for any future newlines.</summary>
            NoIncreaseIndent  = 0x00100000,

            // The following flags are passed through all child rendering calls without being automatically cleared:

            /// <summary>Update LineNumber and Column properties while rendering.</summary>
            UpdateLineCol     = 0x00800000,
            /// <summary>Suppress brackets when rendering a TypeRef for an array type (used for NewArray with jagged arrays).</summary>
            SuppressBrackets  = 0x01000000,
            /// <summary>Render comments in-line (using block style instead of EOL style).</summary>
            CommentsInline    = 0x02000000,
            /// <summary>Render as description (no body, show full signature on references, etc.).</summary>
            Description       = 0x04000000,
            /// <summary>Show any parent types of the type being rendered.</summary>
            ShowParentTypes   = 0x08000000,
            /// <summary>Object being rendered is inside a documentation comment.</summary>
            InDocComment      = 0x10000000,
            /// <summary>Suppress rendering of any prefix annotations on the current object (used to suppress them for the first item in a ChildList).</summary>
            NoPreAnnotations  = 0x20000000,
            /// <summary>Suppress rendering of first/last newlines in doc comment content (used by DocComment classes).</summary>
            NoTagNewLines     = 0x40000000,
            /// <summary>Format numerics in hex.</summary>
            FormatAsHex       = 0x80000000,

            /// <summary>Mask of flags that propagate through all rendering calls.</summary>
            PassMask          = UpdateLineCol | SuppressBrackets | CommentsInline | Description | ShowParentTypes | InDocComment | NoPreAnnotations | NoTagNewLines | FormatAsHex,

            /// <summary>Flags used during length determination for alignment purposes.</summary>
            LengthFlags       = NoPreAnnotations | NoEOLComments | NoPostAnnotations
        }

        /// <summary>
        /// Render the type of the code object and its description as a string.
        /// </summary>
        /// <remarks>
        /// Only a description is rendered, without any Body - otherwise debugging sessions would be too slow
        /// as entire code object trees are rendered.
        /// </remarks>
        public override string ToString()
        {
            return GetType().Name + ": " + GetDescription();
        }

        /// <summary>
        /// Render the entire code object as a string, using LFs for newlines.
        /// </summary>
        /// <remarks>
        /// We don't do this in ToString(), because we don't want the debugger evaluating entire code object
        /// trees when displaying the values of variables.
        /// </remarks>
        public string AsString()
        {
            return AsText(RenderFlags.IncreaseIndent);
        }

#if DEBUG
        /// <summary>
        /// This property is just to make debugging easier.
        /// </summary>
        public string _AsString
        {
            get { return AsString(); }
        }
#endif

        /// <summary>
        /// True if the <see cref="CodeObject"/> is renderable.
        /// </summary>
        public virtual bool IsRenderable
        {
            get { return true; }
        }

        /// <summary>
        /// Get a short text description of the <see cref="CodeObject"/>.
        /// This is generally the shortest text representation that uniquely identifies objects, even if
        /// they have the same name, for example: type or return type, name, type parameters, parameters.
        /// </summary>
        public string GetDescription()
        {
            // Don't set the Description flag for DocComments, because we want to render tags if we're getting
            // the direct description of a DocComment object (but NOT for children DocComments of the target object).
            RenderFlags renderFlags = RenderFlags.ShowParentTypes | RenderFlags.IncreaseIndent;
            if (!(this is DocComment))
                renderFlags |= RenderFlags.Description;
            return AsText(renderFlags);
        }

        /// <summary>
        /// Update the line and column numbers according to the current positions in the <see cref="CodeWriter"/>,
        /// if the <see cref="RenderFlags.UpdateLineCol"/> flag is set.
        /// </summary>
        protected internal virtual void UpdateLineCol(CodeWriter writer, RenderFlags flags)
        {
            if (flags.HasFlag(RenderFlags.UpdateLineCol))
            {
                // Use this for testing that updated Line/Cols match the parsed values (on FullTest.cs):
                //if (writer.LineNumber != _lineNumber || writer.ColumnNumber != _columnNumber)
                //    Log.WriteLine("Line-Col: " + writer.LineNumber + "-" + writer.ColumnNumber + " != " + _lineNumber + "-" + _columnNumber);
                _lineNumber = writer.LineNumber;
                _columnNumber = (ushort)writer.ColumnNumber;
            }
        }

        /// <summary>
        /// Convert the code object to text using the specified flags and format (file or string).
        /// </summary>
        public string AsText(RenderFlags flags, bool isFileFormat, Stack<CodeWriter.AlignmentState> alignmentStateStack)
        {
            using (CodeWriter writer = new CodeWriter(false, IsGenerated))
            {
                if (alignmentStateStack != null)
                    writer.AlignmentStateStack = new Stack<CodeWriter.AlignmentState>(alignmentStateStack);
                try
                {
                    // Render the text into a string, suppressing any leading newline.
                    // Use CR/LFs for file format with a trailing newline, or only LFs for string format.
                    if (!isFileFormat)
                        writer.NewLine = "\n";
                    AsText(writer, flags | RenderFlags.SuppressNewLine);
                    if (isFileFormat)
                        writer.WriteLine();
                }
                catch (Exception ex)
                {
                    string message = Log.Exception(ex, "rendering");
                    writer.Write(message);
                }
                return writer.ToString();
            }
        }

        /// <summary>
        /// Convert the code object to text using the specified flags and format (file or string).
        /// </summary>
        public string AsText(RenderFlags flags, bool isFileFormat)
        {
            return AsText(flags, isFileFormat, null);
        }

        /// <summary>
        /// Convert the code object to text using the specified flags and format (file or string).
        /// </summary>
        public string AsText(RenderFlags flags)
        {
            return AsText(flags, false, null);
        }

        /// <summary>
        /// Convert the code object to text with a trailing newline, and using CR/LF pairs for newlines (file format).
        /// </summary>
        public string AsText()
        {
            return AsText(RenderFlags.None, true);
        }

        /// <summary>
        /// Determine the length of the code object if converted to a string using the specified flags.
        /// </summary>
        public int AsTextLength(RenderFlags flags, Stack<CodeWriter.AlignmentState> alignmentStateStack)
        {
            using (CodeWriter writer = new CodeWriter(true))
            {
                if (alignmentStateStack != null)
                    writer.AlignmentStateStack = new Stack<CodeWriter.AlignmentState>(alignmentStateStack);
                try
                {
                    // Render the text into a string, suppressing any leading newline.
                    // Use only LFs for string format.
                    writer.NewLine = "\n";
                    AsText(writer, flags | RenderFlags.SuppressNewLine);
                }
                catch
                { }
                return writer.ColumnNumber;
            }
        }

        /// <summary>
        /// Determine the length of the code object if converted to a string using the specified flags.
        /// </summary>
        public int AsTextLength(RenderFlags flags)
        {
            return AsTextLength(flags, null);
        }

        /// <summary>
        /// Determine the length of the code object if converted to a string using the specified flags.
        /// </summary>
        public int AsTextLength()
        {
            return AsTextLength(RenderFlags.LengthFlags, null);
        }

        public virtual void AsText(CodeWriter writer, RenderFlags flags)
        {
            UpdateLineCol(writer, flags);
            writer.Write(base.ToString());
        }

        protected virtual void AsTextBefore(CodeWriter writer, RenderFlags flags)
        {
            if (!flags.HasFlag(RenderFlags.NoPreAnnotations))
                AsTextAnnotations(writer, flags);
        }

        protected virtual void AsTextAfter(CodeWriter writer, RenderFlags flags)
        {
            if (!flags.HasFlag(RenderFlags.NoPostAnnotations))
                AsTextAnnotations(writer, AnnotationFlags.IsPostfix, flags);
        }

        protected void AsTextEvaluatedType(CodeWriter writer, SymbolicRef typeRef)
        {
            writer.Write(" { ");
            typeRef.AsText(writer, RenderFlags.Description | RenderFlags.ShowParentTypes);  // Description flag will show any constant value
            writer.Write(" }");
        }

        /// <summary>
        /// Check for alignment of any EOL comments.
        /// </summary>
        protected void CheckForAlignment(CodeWriter writer)
        {
            if (HasEOLComments && IsFirstOnLine && IsSingleLine)
            {
                BlockStatement parentBlockStatement = FindParent<BlockStatement>();
                if (parentBlockStatement != null)
                {
                    int[] columnWidths = writer.GetColumnWidths(parentBlockStatement.Body);
                    if (columnWidths != null)
                    {
                        int columnWidth = Enumerable.Last(columnWidths);
                        if (columnWidth > 0)
                        {
                            int padding = columnWidth - AsTextLength();
                            if (padding > 0)
                                writer.Write(new string(' ', padding));
                        }
                    }
                }
            }
        }

        #region /* ANNOTATION RENDERING */

        /// <summary>
        /// Render all regular (non-EOL, non-Infix, non-Postfix, non-Message) annotations (comments, attributes, compiler directives).
        /// </summary>
        public void AsTextAnnotations(CodeWriter writer, RenderFlags flags)
        {
            if (_annotations != null && !flags.HasFlag(RenderFlags.Description))
            {
                flags |= RenderFlags.IsPrefix;
                foreach (Annotation annotation in _annotations)
                {
                    if (!annotation.IsEOL && !annotation.IsInfix && !annotation.IsPostfix && !(annotation is Message))
                        annotation.AsText(writer, flags | (annotation.IsFirstOnLine ? 0 : RenderFlags.CommentsInline));
                }
            }
        }

        /// <summary>
        /// Render all EOL comments.
        /// </summary>
        public void AsTextEOLComments(CodeWriter writer, RenderFlags flags)
        {
            if (_annotations != null && !flags.HasFlag(RenderFlags.NoEOLComments) && !flags.HasFlag(RenderFlags.Description))
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is Comment && annotation.IsEOL && !annotation.IsInfix)
                        writer.WritePendingEOLComment((Comment)annotation, flags);
                }
            }
        }

        /// <summary>
        /// Render all Infix EOL comments.
        /// </summary>
        public void AsTextInfixEOLComments(CodeWriter writer, RenderFlags flags)
        {
            if (_annotations != null && !flags.HasFlag(RenderFlags.NoEOLComments) && !flags.HasFlag(RenderFlags.Description))
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is Comment && annotation.IsEOL && annotation.IsInfix)
                        writer.WritePendingEOLComment((Comment)annotation, flags);
                }
            }
        }

        /// <summary>
        /// Render all Infix comments with the specified mask.
        /// </summary>
        public void AsTextInfixComments(CodeWriter writer, AnnotationFlags infixMask, RenderFlags flags)
        {
            if (_annotations != null && !flags.HasFlag(RenderFlags.NoEOLComments) && !flags.HasFlag(RenderFlags.Description))
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation is Comment)
                    {
                        if (infixMask != 0 ? (annotation.AnnotationFlags & AnnotationFlags.InfixMask) == infixMask : annotation.IsInfix)
                        {
                            if (flags.HasFlag(RenderFlags.PrefixSpace) && !annotation.IsEOL)
                                writer.Write(" ");
                            writer.WritePendingEOLComment((Comment)annotation, flags);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Render all specified Infix or Postfix annotations (comments, compiler directives).
        /// </summary>
        public void AsTextAnnotations(CodeWriter writer, AnnotationFlags positionFlag, RenderFlags flags)
        {
            if (_annotations != null && !flags.HasFlag(RenderFlags.Description))
            {
                foreach (Annotation annotation in _annotations)
                {
                    if (annotation.AnnotationFlags.HasFlag(positionFlag))
                    {
                        if (annotation is Comment)
                            writer.WritePendingEOLComment((Comment)annotation, flags);
                        else
                            annotation.AsText(writer, RenderFlags.None);
                    }
                }
            }
        }

        #endregion

        #endregion /* RENDERING */
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Software Developer (Senior)
United States United States
I've been writing software since the late 70's, currently focusing mainly on C#.NET. I also like to travel around the world, and I own a Chocolate Factory (sadly, none of my employees are oompa loompas).

Comments and Discussions