/* Function: HasAnyTag * Whether the passed block of text contains any Javadoc tags at all. */ protected bool HasAnyTag(TokenIterator start, TokenIterator end) { string text = start.Tokenizer.RawText; int endIndex = end.RawTextIndex; int textIndex = text.IndexOf('@', start.RawTextIndex, endIndex - start.RawTextIndex); while (textIndex != -1) { start.NextByCharacters(textIndex - start.RawTextIndex); start.Next(); if (textIndex > 0 && text[textIndex - 1] == '{') { if (InlineTags.Contains(start.String)) { return(true); } } else { if (BlockTags.Contains(start.String)) { return(true); } } textIndex = text.IndexOf('@', textIndex + 1, endIndex - (textIndex + 1)); } return(false); }
/* Function: TryToSkipBlockComment * If the iterator is on the opening symbol of a block comment, skips over it and returns true. Otherwise leaves the iterator * alone and returns false. */ protected bool TryToSkipBlockComment(ref TokenIterator iterator, string openingSymbol, string closingSymbol) { if (iterator.MatchesAcrossTokens(openingSymbol)) { iterator.NextByCharacters(openingSymbol.Length); while (iterator.IsInBounds && !iterator.MatchesAcrossTokens(closingSymbol)) { iterator.Next(); } if (iterator.IsInBounds) { iterator.NextByCharacters(closingSymbol.Length); } return(true); } else { return(false); } }
/* Function: TryToGetLineComment * * If the iterator is on a line that starts with a line comment symbol, this function moves the iterator past the entire * comment and returns true. If the comment is a candidate for documentation it will also return it as a * <PossibleDocumentationComment> and mark the symbols as <CommentParsingType.CommentSymbol>. If the * line does not start with a line comment symbol it will return false and leave the iterator where it is. * * This function takes a separate comment symbol for the first line and all remaining lines, allowing you to detect * Javadoc line comments that start with ## and the remaining lines use #. Both symbols can be the same if this isn't * required. * * If openingMustBeAlone is set, no symbol can appear immediately after the first line symbol. If it does the function * will return false and not move past the comment. This allows you to specifically detect something like ## without * also matching #######. */ protected bool TryToGetLineComment(ref LineIterator lineIterator, string firstSymbol, string remainderSymbol, bool openingMustBeAlone, out PossibleDocumentationComment comment) { TokenIterator firstToken = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (firstToken.MatchesAcrossTokens(firstSymbol) == false) { comment = null; return(false); } if (openingMustBeAlone) { TokenIterator nextToken = firstToken; nextToken.NextByCharacters(firstSymbol.Length); if (nextToken.FundamentalType == FundamentalType.Symbol) { comment = null; return(false); } } comment = new PossibleDocumentationComment(); comment.Start = lineIterator; lineIterator.Next(); // Since we're definitely returning a comment we can mark the comment symbols as we go rather than waiting until // the end. firstToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, firstSymbol.Length); while (lineIterator.IsInBounds) { firstToken = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (firstToken.MatchesAcrossTokens(remainderSymbol) == false) { break; } firstToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, remainderSymbol.Length); lineIterator.Next(); } comment.End = lineIterator; return(true); }
/* Function: TryToSkipLocaleSubstitutionIdentifier * If the iterator is on a valid locale substitution identifier, advances it past it, returns it, and returns true. * Otherwise the iterator will be left alone and it will return false. */ protected bool TryToSkipLocaleSubstitutionIdentifier(ref TokenIterator iterator, out string identifier, out string localeIdentifier) { if (iterator.Character != '$' && iterator.Character != '@') { identifier = null; localeIdentifier = null; return(false); } TokenIterator lookahead = iterator; lookahead.Next(); if (lookahead.MatchesAcrossTokens("Locale{") == false) { identifier = null; localeIdentifier = null; return(false); } lookahead.NextByCharacters(7); TokenIterator startOfLocaleIdentifier = lookahead; while (lookahead.IsInBounds && lookahead.Character != '}') { lookahead.Next(); } if (lookahead.Character != '}') { identifier = null; localeIdentifier = null; return(false); } localeIdentifier = startOfLocaleIdentifier.TextBetween(lookahead); lookahead.Next(); identifier = iterator.TextBetween(lookahead); iterator = lookahead; return(true); }
/* Function: TryToSkipLineComment * If the iterator is on the opening symbol of a line comment, skips over it and returns true. Otherwise leaves the iterator * alone and returns false. */ protected bool TryToSkipLineComment(ref TokenIterator iterator, string symbol) { if (iterator.MatchesAcrossTokens(symbol)) { iterator.NextByCharacters(symbol.Length); while (iterator.IsInBounds && iterator.FundamentalType != FundamentalType.LineBreak) { iterator.Next(); } if (iterator.FundamentalType == FundamentalType.LineBreak) { iterator.Next(); } return(true); } else { return(false); } }
/* Function: TryToGetBlockSymbol * If the iterator is on the symbol part of a block tag that has one, such as "@param symbol description", extracts the symbol, * moves the iterator past it, and returns true. */ protected bool TryToGetBlockSymbol(ref TokenIterator iterator, TokenIterator limit, out string entryText) { TokenIterator lookahead = iterator; // Javadoc recommends documenting template parameters as "@param <T> ...". if (lookahead.Character == '<') { lookahead.Next(); for (;;) { if (lookahead >= limit) { entryText = null; return(false); } else if (lookahead.Character == '>') { lookahead.Next(); break; } else { lookahead.Next(); } } } else // not '<' { for (;;) { if (lookahead >= limit) { entryText = null; return(false); } else if (lookahead.FundamentalType == FundamentalType.Text || lookahead.Character == '_' || lookahead.Character == '.') { lookahead.Next(); } else if (lookahead.MatchesAcrossTokens("::") || lookahead.MatchesAcrossTokens("->")) { lookahead.NextByCharacters(2); } else { break; } } } if (lookahead >= limit || lookahead.FundamentalType != FundamentalType.Whitespace) { entryText = null; return(false); } entryText = iterator.TextBetween(lookahead); iterator = lookahead; return(true); }
// Group: Support Functions // __________________________________________________________________________ /* Function: TryToGetPDBlockComment * * If the line iterator is on the starting symbol of a block comment, return it as a <PossibleDocumentationComment> * and mark the symbols as <CommentParsingType.CommentSymbol>. If the iterator is not on the opening comment * symbol or there is content after the closing comment symbol making it unsuitable as a documentation comment, * returns null. * * If openingMustBeAlone is set, that means no symbol can appear immediately after the opening symbol for this * function to succeed. This allows you to specifically detect something like /** without also matching /******. */ protected PossibleDocumentationComment TryToGetPDBlockComment(LineIterator lineIterator, string openingSymbol, string closingSymbol, bool openingMustBeAlone) { TokenIterator firstToken = lineIterator.FirstToken(LineBoundsMode.ExcludeWhitespace); if (firstToken.MatchesAcrossTokens(openingSymbol) == false) { return(null); } if (openingMustBeAlone) { TokenIterator nextToken = firstToken; nextToken.NextByCharacters(openingSymbol.Length); if (nextToken.FundamentalType == FundamentalType.Symbol) { return(null); } } PossibleDocumentationComment comment = new PossibleDocumentationComment(); comment.Start = lineIterator; for (;;) { if (!lineIterator.IsInBounds) { return(null); } TokenIterator closingSymbolIterator; if (lineIterator.FindAcrossTokens(closingSymbol, false, LineBoundsMode.Everything, out closingSymbolIterator) == true) { closingSymbolIterator.NextByCharacters(closingSymbol.Length); closingSymbolIterator.NextPastWhitespace(); if (closingSymbolIterator.FundamentalType != FundamentalType.LineBreak && closingSymbolIterator.FundamentalType != FundamentalType.Null) { return(null); } lineIterator.Next(); comment.End = lineIterator; break; } lineIterator.Next(); } // Success. Mark the symbols before returning. firstToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, openingSymbol.Length); TokenIterator lastToken; lineIterator.Previous(); lineIterator.GetBounds(LineBoundsMode.ExcludeWhitespace, out firstToken, out lastToken); lastToken.PreviousByCharacters(closingSymbol.Length); lastToken.SetCommentParsingTypeByCharacters(CommentParsingType.CommentSymbol, closingSymbol.Length); return(comment); }
/* Function: HasSimilarSpacing * Returns whether the spacing of the tokens between the two iterators matches what would have been built by this * class if the tokens were passed through it. */ public static bool HasSimilarSpacing(TokenIterator start, TokenIterator end) { // ::Package::Name* array of const*[] // Single spaces only between words, and between words and prior symbols except for leading symbols and package separators TokenIterator iterator = start; bool pastFirstText = false; FundamentalType lastTokenType = FundamentalType.Null; bool dontAddSpaceAfterSymbol = false; bool lastSymbolWasBlock = false; while (iterator < end) { if (iterator.FundamentalType == FundamentalType.Text || iterator.Character == '_') { if (lastTokenType == FundamentalType.Null || lastTokenType == FundamentalType.Text || (lastTokenType == FundamentalType.Symbol && (dontAddSpaceAfterSymbol || (!pastFirstText && !lastSymbolWasBlock))) || lastTokenType == FundamentalType.Whitespace) { pastFirstText = true; lastTokenType = FundamentalType.Text; if (!TryToSkipModifierBlock(ref iterator)) { iterator.Next(); } } else { return(false); } } else if (iterator.FundamentalType == FundamentalType.Symbol) { if (iterator.Character == ',') { // Quit early on commas, since it could be x[,,] or (x, y), in which case it's not clear whether there should be // a space without making this logic even more complicated. Just fail out and build a new one. return(false); } if (lastTokenType == FundamentalType.Null || lastTokenType == FundamentalType.Text || lastTokenType == FundamentalType.Symbol) { lastTokenType = FundamentalType.Symbol; if (iterator.MatchesAcrossTokens("::")) { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = true; iterator.NextByCharacters(2); } else if (iterator.Character == '.' || iterator.Character == '%' || iterator.Character == '"' || iterator.Character == '\'' || iterator.Character == '@' || iterator.Character == '(') { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = true; iterator.Next(); } else if (TryToSkipModifierBlock(ref iterator)) { lastSymbolWasBlock = true; dontAddSpaceAfterSymbol = false; // already moved iterator } else { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = false; iterator.Next(); } } else { return(false); } } else if (iterator.FundamentalType == FundamentalType.Whitespace && iterator.Character == ' ' && iterator.RawTextLength == 1) { if ((lastTokenType == FundamentalType.Symbol && !dontAddSpaceAfterSymbol && (pastFirstText || lastSymbolWasBlock)) || lastTokenType == FundamentalType.Text) { lastTokenType = FundamentalType.Whitespace; iterator.Next(); } else { return(false); } } else { return(false); } } return(true); }
/* Function: HasSimilarSpacing * Returns whether the spacing of the tokens between the two iterators matches what would have been built by this * class if the tokens were passed through it. */ public static bool HasSimilarSpacing(TokenIterator start, TokenIterator end) { // ::Package::Name* array of const*[] // Single spaces only between words, and between words and prior symbols except for leading symbols and package separators TokenIterator iterator = start; bool pastFirstText = false; FundamentalType lastTokenType = FundamentalType.Null; bool dontAddSpaceAfterSymbol = false; bool lastSymbolWasBlock = false; while (iterator < end) { if (iterator.FundamentalType == FundamentalType.Text || iterator.Character == '_') { if (lastTokenType == FundamentalType.Null || lastTokenType == FundamentalType.Text || (lastTokenType == FundamentalType.Symbol && (dontAddSpaceAfterSymbol || (!pastFirstText && !lastSymbolWasBlock))) || lastTokenType == FundamentalType.Whitespace) { pastFirstText = true; lastTokenType = FundamentalType.Text; if (!TryToSkipModifierBlock(ref iterator)) { iterator.Next(); } } else { return(false); } } else if (iterator.FundamentalType == FundamentalType.Symbol) { if (lastTokenType == FundamentalType.Null || lastTokenType == FundamentalType.Text || lastTokenType == FundamentalType.Symbol) { lastTokenType = FundamentalType.Symbol; if (iterator.MatchesAcrossTokens("::")) { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = true; iterator.NextByCharacters(2); } else if (iterator.Character == '.' || iterator.Character == '%') { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = true; iterator.Next(); } else if (TryToSkipModifierBlock(ref iterator)) { lastSymbolWasBlock = true; dontAddSpaceAfterSymbol = false; // already moved iterator } else { lastSymbolWasBlock = false; dontAddSpaceAfterSymbol = false; iterator.Next(); } } else { return(false); } } else if (iterator.FundamentalType == FundamentalType.Whitespace && iterator.Character == ' ' && iterator.RawTextLength == 1) { if ((lastTokenType == FundamentalType.Symbol && !dontAddSpaceAfterSymbol && (pastFirstText || lastSymbolWasBlock)) || lastTokenType == FundamentalType.Text) { lastTokenType = FundamentalType.Whitespace; iterator.Next(); } else { return(false); } } else { return(false); } } return(true); }
// Group: Private Functions // __________________________________________________________________________ /* Function: DetermineElement * Determines which <JavadocElementType> the iterator is currently on, setting <type> and <length>. */ private void DetermineElement() { bool found = false; if (tokenIterator == null || !IsInBounds) { type = JavadocElementType.OutOfBounds; length = 0; found = true; } // If we're on a new line and either whitespace or a comment token... else if ((RawTextIndex == 0 || Tokenizer.FundamentalTypeOf(RawText[RawTextIndex - 1]) == FundamentalType.LineBreak) && (tokenIterator.FundamentalType == FundamentalType.Whitespace || tokenIterator.CommentParsingType == CommentParsingType.CommentSymbol || tokenIterator.CommentParsingType == CommentParsingType.CommentDecoration)) { type = JavadocElementType.Indent; length = 0; TokenIterator lookahead = tokenIterator; do { length += lookahead.RawTextLength; lookahead.Next(); }while (lookahead.RawTextIndex < endingRawTextIndex && (lookahead.FundamentalType == FundamentalType.Whitespace || lookahead.CommentParsingType == CommentParsingType.CommentSymbol || lookahead.CommentParsingType == CommentParsingType.CommentDecoration)); found = true; } else if (tokenIterator.FundamentalType == FundamentalType.LineBreak) { type = JavadocElementType.LineBreak; length = tokenIterator.RawTextLength; found = true; } else if (tokenIterator.MatchesAcrossTokens("<!--")) { type = JavadocElementType.HTMLComment; int endingCommentIndex = RawText.IndexOf("-->", RawTextIndex + 4, endingRawTextIndex - (RawTextIndex + 4)); if (endingCommentIndex == -1) { length = endingRawTextIndex - RawTextIndex; } else { length = (endingCommentIndex + 3) - RawTextIndex; } found = true; } else if (tokenIterator.Character == '<') { int nextBracketIndex = RawText.IndexOfAny(AngleBrackets, RawTextIndex + 1); if (nextBracketIndex != -1 && nextBracketIndex < endingRawTextIndex && RawText[nextBracketIndex] == '>') { type = JavadocElementType.HTMLTag; length = nextBracketIndex + 1 - RawTextIndex; Match tagMatch = HTMLTagRegex.Match(RawText, RawTextIndex, length); if (tagMatch.Success) { found = true; tagType = tagMatch.Groups[1].ToString().ToLower(); } } } else if (tokenIterator.MatchesAcrossTokens("{@")) { int closingBrace = RawText.IndexOf('}', RawTextIndex + 2); TokenIterator lookahead = tokenIterator; lookahead.NextByCharacters(2); if (closingBrace != -1 && closingBrace < endingRawTextIndex && lookahead.FundamentalType == FundamentalType.Text && Parsers.Javadoc.InlineTags.Contains(lookahead.String)) { found = true; type = JavadocElementType.JavadocTag; tagType = lookahead.String; length = (closingBrace + 1) - RawTextIndex; } } else if (tokenIterator.Character == '@') { TokenIterator lookahead = tokenIterator; lookahead.Next(); if (lookahead.FundamentalType == FundamentalType.Text && Parsers.Javadoc.BlockTags.Contains(lookahead.String)) { found = true; type = JavadocElementType.JavadocTag; tagType = lookahead.String; length = tagType.Length + 1; } } else if (tokenIterator.Character == '&') { int semicolonIndex = RawText.IndexOf(';', RawTextIndex + 1); if (semicolonIndex != -1 && semicolonIndex < endingRawTextIndex && HTMLEntityChars.IsEntityChar(RawText, RawTextIndex, semicolonIndex + 1 - RawTextIndex)) { type = JavadocElementType.EntityChar; length = semicolonIndex + 1 - RawTextIndex; found = true; } } if (!found) { type = JavadocElementType.Text; int nextElementIndex = RawText.IndexOfAny(StartOfElement, RawTextIndex + 1); if (nextElementIndex == -1) { length = endingRawTextIndex - RawTextIndex; } else { length = nextElementIndex - RawTextIndex; } } }