/// <summary>
        /// Gets a value indicating whether the <paramref name="token"/> is the first token in a line and it is only preceded by whitespace.
        /// </summary>
        /// <param name="token">The token to process.</param>
        /// <returns>true if the token is the first token in a line and it is only preceded by whitespace.</returns>
        internal static bool IsOnlyPrecededByWhitespaceInLine(this SyntaxToken token)
        {
            SyntaxToken precedingToken = token.GetPreviousToken();

            if (!precedingToken.IsKind(SyntaxKind.None) && (precedingToken.GetLine() == token.GetLine()))
            {
                return(false);
            }

            var precedingTriviaList = TriviaHelper.MergeTriviaLists(precedingToken.TrailingTrivia, token.LeadingTrivia);

            for (var i = precedingTriviaList.Count - 1; i >= 0; i--)
            {
                switch (precedingTriviaList[i].Kind())
                {
                case SyntaxKind.WhitespaceTrivia:
                    break;

                case SyntaxKind.EndOfLineTrivia:
                    return(true);

                default:
                    return(false);
                }
            }

            return(true);
        }
Beispiel #2
0
        /// <summary>
        /// Checks if a given <see cref="SyntaxTree"/> only contains whitespace. We don't want to analyze empty files.
        /// </summary>
        /// <param name="tree">The syntax tree to examine.</param>
        /// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="tree"/> only contains whitespace; otherwise,
        /// <see langword="false"/>.
        /// </returns>
        public static bool IsWhitespaceOnly(this SyntaxTree tree, CancellationToken cancellationToken)
        {
            var root       = tree.GetRoot(cancellationToken);
            var firstToken = root.GetFirstToken(includeZeroWidth: true);

            return(firstToken.IsKind(SyntaxKind.EndOfFileToken) &&
                   TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia) == -1);
        }
        internal static XmlFileHeader ParseXmlFileHeader(SyntaxNode root)
        {
            var    firstToken = root.GetFirstToken(includeZeroWidth: true);
            string xmlString;
            int    fileHeaderStart;
            int    fileHeaderEnd;

            var firstNonWhitespaceTrivia = TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia, true);

            if (firstNonWhitespaceTrivia == -1)
            {
                return(XmlFileHeader.MissingFileHeader);
            }

            switch (firstToken.LeadingTrivia[firstNonWhitespaceTrivia].Kind())
            {
            case SyntaxKind.SingleLineCommentTrivia:
                xmlString = ProcessSingleLineCommentsHeader(firstToken.LeadingTrivia, firstNonWhitespaceTrivia, out fileHeaderStart, out fileHeaderEnd);
                break;

            case SyntaxKind.MultiLineCommentTrivia:
                xmlString = ProcessMultiLineCommentsHeader(firstToken.LeadingTrivia[firstNonWhitespaceTrivia], out fileHeaderStart, out fileHeaderEnd);
                break;

            default:
                return(XmlFileHeader.MissingFileHeader);
            }

            if (fileHeaderStart > fileHeaderEnd)
            {
                return(XmlFileHeader.MissingFileHeader);
            }

            try
            {
                var parsedFileHeaderXml = XElement.Parse(xmlString);

                // a header without any XML tags is malformed.
                if (!parsedFileHeaderXml.Descendants().Any())
                {
                    return(XmlFileHeader.MalformedFileHeader);
                }

                return(new XmlFileHeader(parsedFileHeaderXml, fileHeaderStart, fileHeaderEnd));
            }
            catch (XmlException)
            {
                return(XmlFileHeader.MalformedFileHeader);
            }
        }
        internal static FileHeader ParseFileHeader(SyntaxNode root)
        {
            var firstToken = root.GetFirstToken(includeZeroWidth: true);
            var firstNonWhitespaceTrivia = TriviaHelper.IndexOfFirstNonWhitespaceTrivia(firstToken.LeadingTrivia, true);

            if (firstNonWhitespaceTrivia == -1)
            {
                return(FileHeader.MissingFileHeader);
            }

            var sb              = StringBuilderPool.Allocate();
            var endOfLineCount  = 0;
            var done            = false;
            var fileHeaderStart = int.MaxValue;
            var fileHeaderEnd   = int.MinValue;

            for (var i = firstNonWhitespaceTrivia; !done && (i < firstToken.LeadingTrivia.Count); i++)
            {
                var trivia = firstToken.LeadingTrivia[i];

                switch (trivia.Kind())
                {
                case SyntaxKind.WhitespaceTrivia:
                    endOfLineCount = 0;
                    break;

                case SyntaxKind.SingleLineCommentTrivia:
                    endOfLineCount = 0;

                    var commentString = trivia.ToFullString();

                    fileHeaderStart = Math.Min(trivia.FullSpan.Start, fileHeaderStart);
                    fileHeaderEnd   = trivia.FullSpan.End;

                    sb.AppendLine(commentString.Substring(2).Trim());
                    break;

                case SyntaxKind.MultiLineCommentTrivia:
                    // only process a MultiLineCommentTrivia if no SingleLineCommentTrivia have been processed
                    if (sb.Length == 0)
                    {
                        var triviaString = trivia.ToFullString();

                        var startIndex = triviaString.IndexOf("/*", StringComparison.Ordinal) + 2;
                        var endIndex   = triviaString.LastIndexOf("*/", StringComparison.Ordinal);
                        if (endIndex == -1)
                        {
                            // While editing, it is possible to have a multiline comment trivia that does not contain the closing '*/' yet.
                            return(FileHeader.MissingFileHeader);
                        }

                        var commentContext = triviaString.Substring(startIndex, endIndex - startIndex).Trim();

                        var triviaStringParts = commentContext.Replace("\r\n", "\n").Split('\n');

                        foreach (var part in triviaStringParts)
                        {
                            var trimmedPart = part.TrimStart(' ', '*');
                            sb.AppendLine(trimmedPart);
                        }

                        fileHeaderStart = trivia.FullSpan.Start;
                        fileHeaderEnd   = trivia.FullSpan.End;
                    }

                    done = true;
                    break;

                case SyntaxKind.EndOfLineTrivia:
                    endOfLineCount++;
                    done = endOfLineCount > 1;
                    break;

                default:
                    done = (fileHeaderStart < fileHeaderEnd) || !trivia.IsDirective;
                    break;
                }
            }

            if (fileHeaderStart > fileHeaderEnd)
            {
                StringBuilderPool.Free(sb);
                return(FileHeader.MissingFileHeader);
            }

            if (sb.Length > 0)
            {
                // remove the final newline
                var eolLength = Environment.NewLine.Length;
                sb.Remove(sb.Length - eolLength, eolLength);
            }

            return(new FileHeader(StringBuilderPool.ReturnAndFree(sb), fileHeaderStart, fileHeaderEnd));
        }