/// <summary> /// Gets the HTML class name for the specified syntax trivia. /// </summary> /// <param name="trivia">The <see cref="SyntaxTrivia"/> object for which a <c>HtmlClassName</c> enumeration /// value is returned.</param> /// <returns>A value of the <see cref="HtmlClassName"/> enumeration.</returns> protected virtual HtmlClassName GetHtmlClass(SyntaxTrivia trivia) { HtmlClassName className = HtmlClassName.Unknown; switch (trivia.Kind()) { case SyntaxKind.SingleLineCommentTrivia: case SyntaxKind.MultiLineCommentTrivia: case SyntaxKind.DocumentationCommentExteriorTrivia: className = HtmlClassName.Comment; break; case SyntaxKind.DisabledTextTrivia: className = HtmlClassName.DisabledText; break; case SyntaxKind.PreprocessingMessageTrivia: className = HtmlClassName.None; break; default: className = HtmlClassName.None; break; } return(className); }
/// <summary> /// Gets the HTML class name for the specified token or trivia wrapped by the <see cref="SyntaxTreeElement"/> instance. /// </summary> /// <remarks> /// The HTML class name is used to create a class attribute for a "span" tag that wraps the trivia or token if the /// value is not <c>HtmlClassName.None</c> or <c>HtmlClassName.Unknown</c>. /// </remarks> /// <param name="treeElement">The tree element.</param> /// <returns>A value of the <see cref="HtmlClassName"/> enumeration.</returns> /// <exception cref="ArgumentNullException">if <paramref name="treeElement"/> is <c>null</c>.</exception> protected virtual HtmlClassName GetHtmlClass(SyntaxTreeElement treeElement) { if (treeElement == null) { throw new ArgumentNullException(nameof(treeElement), "SyntaxTree element cannot be null."); } HtmlClassName className = HtmlClassName.Unknown; switch (treeElement.SyntaxElementCategory) { case SyntaxElementCategory.Token: className = GetHtmlClass(treeElement.Token); break; case SyntaxElementCategory.Trivia: className = GetHtmlClass(treeElement.Trivia); break; default: break; } return(className); }
/// <summary> /// Gets the HTML class name for the specified <see cref="SyntaxToken"/> whose <c>Kind</c> is the /// <c>SyntaxKind.Identifier</c> enumeration value. /// </summary> /// <remarks> /// This method is called by <see cref="GetHtmlClass(SyntaxToken)"/> when the class name for a token cannot be determined /// by the the syntax (<c>SyntaxKind</c> from the syntax tree) alone. This method uses the semantic model to recover /// an <see cref="ISymbol"/> instance and uses that to determine if the <see cref="HtmlClassName.Identifier"/> /// should be used for this token. /// </remarks> /// <param name="token">The identifier token whose class name is returned.</param> /// <returns>Returns the class name for the token. This is a value of the <see cref="HtmlClassName"/> enumeration and is /// used if the token is wrapped in a "span" tag. If <c>HtmlClassName.None</c> is returned, the identifier is not /// wrapped in a "span" tag. The only other values returned are <c>HtmlClassName.Identifier</c> or /// <c>HtmlClassName.UnknownIdentifier.</c></returns> /// <seealso cref="IsIdentifier"/> protected virtual HtmlClassName GetIdentifierTokenHtmlClass(SyntaxToken token) { HtmlClassName className = HtmlClassName.Unknown; ISymbol symbol = token.GetSymbol(SemanticModel); TraceWrite("For identifier token [{0}], symbol is [{1}] ", token.Text, (symbol == null) ? "Null" : symbol.Kind.ToString()); if (symbol != null) { switch (symbol.Kind) { // Named types are highlighted case SymbolKind.NamedType: className = HtmlClassName.Identifier; break; // Methods are only highlighted when specified in attribute statement, for example // <c>[assembly: AssemblyTitle("This Title")]</c> case SymbolKind.Method: className = token.IsInNode(SyntaxKind.Attribute) ? HtmlClassName.Identifier : HtmlClassName.None; break; // The following known SymbolKind values for identifiers are never highlighted case SymbolKind.Namespace: case SymbolKind.Parameter: case SymbolKind.Local: case SymbolKind.Property: case SymbolKind.Field: className = HtmlClassName.None; break; default: break; } } if (className == HtmlClassName.Unknown) { if (InferIdentifier) { className = IsIdentifier(token) ? HtmlClassName.InferredIdentifier : HtmlClassName.None; } else { className = HtmlClassName.UnknownIdentifier; } } return(className); }
/// <summary> /// Gets the HTML class name for the specified syntax token. /// </summary> /// <param name="token">The <see cref="SyntaxToken"/> object for which a <c>HtmlClassName</c> enumeration /// value is returned.</param> /// <returns>A value of the <see cref="HtmlClassName"/> enumeration.</returns> protected virtual HtmlClassName GetHtmlClass(SyntaxToken token) { HtmlClassName className = HtmlClassName.Unknown; if (token.IsKeyword() || SyntaxFacts.IsPreprocessorKeyword(token.Kind())) { className = token.IsInDocumentationCommentTrivia() ? HtmlClassName.DocumentationComment : HtmlClassName.Keyword; } else { switch (token.Kind()) { case SyntaxKind.HashToken: if (token.IsInNode(n => SyntaxFacts.IsPreprocessorDirective(n.Kind()))) { className = HtmlClassName.Keyword; } break; case SyntaxKind.EqualsToken: case SyntaxKind.DoubleQuoteToken: case SyntaxKind.SingleQuoteToken: case SyntaxKind.OpenParenToken: case SyntaxKind.CloseParenToken: case SyntaxKind.CommaToken: case SyntaxKind.DotToken: case SyntaxKind.LessThanToken: case SyntaxKind.GreaterThanToken: case SyntaxKind.LessThanSlashToken: case SyntaxKind.SlashGreaterThanToken: if (token.IsInDocumentationCommentTrivia()) { className = HtmlClassName.DocumentationComment; } break; case SyntaxKind.XmlCDataStartToken: case SyntaxKind.XmlCDataEndToken: case SyntaxKind.XmlTextLiteralToken: case SyntaxKind.XmlEntityLiteralToken: // < > " & ' or &name; or &#nnnn; or &#xhhhh; if (token.IsInNode(SyntaxKind.XmlCDataSection)) { className = HtmlClassName.DocumentationComment; } else if (token.IsInDocumentationCommentTrivia()) { className = HtmlClassName.Comment; } break; case SyntaxKind.StringLiteralToken: className = HtmlClassName.StringLiteral; break; case SyntaxKind.CharacterLiteralToken: className = HtmlClassName.CharacterLiteral; break; case SyntaxKind.NumericLiteralToken: className = HtmlClassName.NumericLiteral; break; case SyntaxKind.IdentifierToken: // If the identifier is part of XML documentation, it is just takes the DocumentComment class name. if (token.IsInDocumentationCommentTrivia()) { className = HtmlClassName.DocumentationComment; } else if (token.IsInNode(n => SyntaxFacts.IsPreprocessorDirective(n.Kind()))) { // If the identifier is part of the preprocessor directive (define, if, etc.), no class is used. className = HtmlClassName.None; } else { className = GetIdentifierTokenHtmlClass(token); } break; default: break; } } return(className); }
/// <summary> /// Processes the token or trivia by returning the text of the token possibly wrapped in a "span" tag with any /// necessary class attribute. /// </summary> /// <remarks> /// This method is called from the <c>CoreRenderer.Callback</c> delegate which is set in the constructor. This method /// and its helper methods, primarily, <see cref="GetText"/> and <see cref="GetHtmlClass(SyntaxTreeElement)"/> /// (and their helper methods) do the heavy lifting to generate the correct HTML code for the C# code text. /// </remarks> /// <param name="syntaxTreeElement">The syntax tree element containing the token or trivia.</param> /// <exception cref="ArgumentNullException">if <paramref name="syntaxTreeElement"/> is <c>null</c>.</exception> protected virtual void ProcessTokenOrTrivia(SyntaxTreeElement syntaxTreeElement) { if (syntaxTreeElement == null) { throw new ArgumentNullException(nameof(syntaxTreeElement), "SyntaxTree element may not be null."); } HtmlClassName className = GetHtmlClass(syntaxTreeElement); string text = GetText(syntaxTreeElement); TraceWriteLine("Class Name: [{0}]; Text: [{1}]", className, text.Replace("\r\n", "\\r\\n")); StringBuilder outputText = new StringBuilder(); if (syntaxTreeElement.SyntaxKind == SyntaxKind.MultiLineCommentTrivia) { string[] lines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); for (int i = 0; i < lines.Length; i++) { string line = lines[i]; outputText.AppendFormat("<span class=\"{0}\"", HtmlClassName.Comment); if (IncludeDebugInfo) { outputText.AppendFormat(" data-syntaxKind=\"{0}\"", syntaxTreeElement.SyntaxKind); } outputText.AppendFormat(">{0}</span>", line); // Because GetLineEndText is called, IsLineEmpty is reset to true (actually GetLineStartText sets it to true // which is called by GetLineEndText. Consequently, IsLineEmpty needs to be set to false for the lines of // comment so that a trailing is not added to the end of the text. Usually this is done in GetText, // but that method is not called here, because we are creating the lines individually. All part of the // complexity of the "old-fashioned" multi-line comments (those marked with "/*" and "*/"). if (line.Length != 0) { IsLineEmpty = false; } if (i < lines.Length - 1) { outputText.Append(GetLineEndText()); } } } else if (className > HtmlClassName.None) { outputText.AppendFormat("<span class=\"{0}\"", className); if (IncludeDebugInfo) { outputText.AppendFormat(" data-syntaxKind=\"{0}\"", syntaxTreeElement.SyntaxKind); } outputText.AppendFormat(">{0}</span>", text); } if (outputText.Length != 0) { text = outputText.ToString(); } Writer.Write(text); }