0

I have created an analyzer which will detect if a method does not contain a <createddate> tag in the xml and a provider that will inject this tag. It works fine and inserts the tag but it still returns with build error even though the rule has been fixed. I do think I need to use semantic models possibly?

This is what I have: CodeFixProvider.cs

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Rename;

namespace NewAnalyzer
{
    [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NewAnalyzerCodeFixProvider)), Shared]
    public class NewAnalyzerCodeFixProvider : CodeFixProvider
    {
        private const string title = "Add Createddate";

        public sealed override ImmutableArray<string> FixableDiagnosticIds
        {
            get { return ImmutableArray.Create(NewAnalyzerAnalyzer.DiagnosticId); }
        }

        public sealed override FixAllProvider GetFixAllProvider()
        {
            // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
            return WellKnownFixAllProviders.BatchFixer;
        }

        public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var diagnostic = context.Diagnostics.First();
            var diagnosticSpan = diagnostic.Location.SourceSpan;
            SyntaxNode root;
            context.Document.TryGetSyntaxRoot(out root);

            var syntax = root.FindNode(diagnostic.Location.SourceSpan);

            var methodDeclarationSyntax = syntax.FirstAncestorOrSelf<MethodDeclarationSyntax>();

            var description = "Add created datetime for API endpoint.";
            var equivalenceKey = "empty string";
            context.RegisterCodeFix(CodeAction.Create(description, cancellationToken => CreateChangedDocument(context, methodDeclarationSyntax, cancellationToken), equivalenceKey), diagnostic);
            return Task.FromResult(0);
        }


        /// <summary>
        /// Create a new method that will contain the changes required to insert a createddate tag into the comments.
        /// </summary>
        /// <param name="context">context</param>
        /// <param name="methodDeclarationSyntax">method declaration syntax</param>
        /// <param name="cancellationToken">cancellation token</param>
        /// <returns>new method that contains createddate in the comments</returns>
        private static async Task<Document> CreateChangedDocument(CodeFixContext context, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken)
        {
            var originalTree = await context.Document.GetSyntaxTreeAsync(cancellationToken);
            var newTree = await context.Document.GetSyntaxTreeAsync(cancellationToken);
            var root = await newTree.GetRootAsync(cancellationToken);

            var documentationComment = methodDeclarationSyntax.GetLeadingTrivia().Select(i => i.GetStructure()).OfType<DocumentationCommentTriviaSyntax>().FirstOrDefault();
            var summaryElement = (XmlElementSyntax)documentationComment.Content.FirstOrDefault(x => x is XmlElementSyntax); // works


            if (documentationComment == null)
                return context.Document;

            var newLineText = SyntaxFactory.XmlTextNewLine(SyntaxFactory.TriviaList(), Environment.NewLine, Environment.NewLine, SyntaxFactory.TriviaList());

            var createdDateText =
            SyntaxFactory.XmlText(SyntaxFactory.TokenList(
                SyntaxFactory.XmlTextLiteral(
                    SyntaxFactory.TriviaList(),
                    DateTime.UtcNow.ToString("d"),
                    DateTime.UtcNow.ToString("d"),
                    SyntaxFactory.TriviaList())
                    ));

            var textList = SyntaxFactory.List<XmlNodeSyntax>(new[] { createdDateText });
            var createdDateNode = new XmlNodeSyntax[]
            {
                SyntaxFactory.XmlText().AddTextTokens(SyntaxFactory.XmlTextNewLine(SyntaxFactory.TriviaList(), Environment.NewLine, Environment.NewLine, SyntaxFactory.TriviaList())),
                    SyntaxFactory.XmlElement(SyntaxFactory.XmlElementStartTag(SyntaxFactory.XmlName("createddate")).WithLeadingTrivia(SyntaxFactory.DocumentationCommentExterior("/// ")),
                                    textList,
                                    SyntaxFactory.XmlElementEndTag(SyntaxFactory.XmlName("createddate"))).WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation)
            };

            var list = SyntaxFactory.List<XmlNodeSyntax>(createdDateNode);
            SyntaxNode tempNode = documentationComment.InsertNodesAfter(summaryElement, list);

            var newRoot = root.ReplaceNode(documentationComment, tempNode);
             var semanticModel = await context.Document.GetSemanticModelAsync(cancellationToken);
             var typeSymbol = semanticModel.GetDeclaredSymbol(methodDeclarationSyntax, cancellationToken);

            var semModel = await context.Document.GetSemanticModelAsync();
            var compilation = semModel.Compilation.ReplaceSyntaxTree(originalTree, newTree);

            var oldSemModel = await context.Document.GetSemanticModelAsync();
            oldSemModel = semanticModel;
            return context.Document;
        }
    }
}

And DiagnosticAnalyzer.cs

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace NewAnalyzer
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class NewAnalyzerAnalyzer : DiagnosticAnalyzer
    {
        public const string DiagnosticId = "NA001";

        // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
        // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
        private static readonly LocalizableString Title = "Title: createddate is missing";
        private static readonly LocalizableString MessageFormat = "Format: createddate is missing";
        private static readonly LocalizableString Description = "Desc: createddate is missing";
        private const string Category = "Naming";

        private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

        public override void Initialize(AnalysisContext context)
        {
            // TODO: Consider registering other actions that act on syntax instead of or in addition to symbols
            // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Analyzer%20Actions%20Semantics.md for more information
            //context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
            context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
        }

        /// <summary>
        /// Analyze method to see if it's a REST endpoint method
        /// </summary>
        /// <param name="context">context</param>
        private static void AnalyzeMethod(SymbolAnalysisContext context)
        {
            var methodDeclarationNode = context.Symbol.GetDocumentationCommentXml();

            if (methodDeclarationNode != null)
            {
                if (!methodDeclarationNode.Contains("createddate"))
                {
                    var diagnostic = Diagnostic.Create(Rule, context.Symbol.Locations[0], methodDeclarationNode);
                    context.ReportDiagnostic(diagnostic);
                }
            }
        }
    }
}

Any ideas as to how to let the build pass when the tag is present in the xml? Seems like it is not really updating the solution?

Java investor
  • 117
  • 3
  • 12

1 Answers1

0

context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method) will execute the callback for property getters and setters too. Your codefix is failing on properties, because it can't find a MethodDeclarationSyntax for the reported location.

Why did you implement this functionality as a symbol based analyzer? It could be a syntax node based one, and you can subscribe to MethodDeclarations.

Also, in the codefix the documentationComment can be null, and you get the Content of it, so that can fail too.

Other than these your codefix adds the current time.

These all come up when you start debugging your extension. Maybe have a look at the Exception Settings window to break on all CLR exceptions.

Tamas
  • 6,260
  • 19
  • 30