/// <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); }
/// <summary> /// Parses an XML-based file header. /// </summary> /// <param name="root">The root of the syntax tree.</param> /// <returns>The parsed file header.</returns> 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); } }
/// <summary> /// Parses a comment-only file header. /// </summary> /// <param name="root">The root of the syntax tree.</param> /// <returns>The copyright string, as parsed from the file header.</returns> 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); 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)); }