Custom rules for Code Upgrade Tool

The Code Upgrade Tool allows you to detect patterns in X++ code and even to automatically change code. It’s especially useful for code upgrade, because you can easily upgrade legacy code. For example, CompanyInfo.CurrencyInfo field was replaced by CompanyInfo::standardCurrency() method in AX 2012. To upgrade your code, you would have to find and fix all occurrences, which would be tedious. Fortunately the Code Upgrade Tools knows how to detect this pattern and how to fix the code for you. And this is still an easy case – you would at least find the problem anyway, because your original code wouldn’t compile. In some other cases, code is still compilable in the new version, but it’s wrong for some reason and you need something more clever to identify it. Also note that the Code Upgrade Tool doesn’t try to fix all the issues – in some cases, it only reports them and it’s up to you to decide how the code should be upgraded.

The tool was first introduced as a separate download for AX 2012 RTM and integrated to the standard installation in version R2. The original name was Code Upgrade Tool, it’s called Code Upgrade Service on InformationSource and it doesn’t seem to have any separate name since integration to AX. I keep calling it Code Upgrade Tool and I recommend the same to you, because that’s how related assemblies and namespaces are named anyway.

I’m using AX 2012 R3 when writing this article, but it all works the same in AX 2012 R2, as far as I know.

To use the tool, first go to the developer workspace and open Tools > Code upgrade > Configure rules for code upgrade. If you don’t see any rules, click Manage code upgrade rules and then Browse. Choose Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Rules.dll file from Dynamics AX client bin folder (typically c:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\) and press Import rules. This imports rules prepared by Microsoft for code upgrade to AX 2012. You can also configure which rules should be used.

RuleConfig

When running code conflict detection (Tools > Code upgrade > Detect code upgrade conflicts), you can detect issues by ticking Create code upgrade patterns project checkbox and run automatic fixes by Auto-resolve code patterns.

DetectConflicts

The cool thing is that you can define your own rules and nothing prevents you from using them for other things than just code upgrade. You can use them for additional static analysis of your code, such as looking for dangerous code patters, and I’m sure it could be used for many different things if enough creative people played with it.

I’m going to show you how to write a simple rule. We found that a developer of our current codebase sometimes updated fields in InventDim table (such as InventLocationId) directly, i.e. not by creating a new InventDimId (or finding existing one). That’s very wrong and we want to be 100%-sure that not a single update like this remains in the application. Calls to update() method can be easily found by cross-references and I hoped to find references to doUpdate() in cross-reference tables as well, but unfortunately they’re not there. Therefore I decided to write a simple rule for Code Upgrade Tool to detect all such calls.

You can find some documentation for writing custom rules (Code Upgrade Tool User Guide [AX 2012]) and even code for rules prepared by Microsoft (downloadable from InformationSource), which will give you plenty of useful examples. The problem is that all this is for the beta version released for AX 2012 RTM and it won’t work with later versions. But let’s take a look anyway, because it’s still a good starting point.

Log on to InformationSource, switch to Services and download the Code Upgrade Service. Extract the archive and ignore everything except CodeUpgradeTool.Rules.zip and CodeUpgradeTool.Rules.Partner.zip. The first file contains code for standard rules implemented by Microsoft (don’t forget – it’s how it looked in the beta version) and CodeUpgradeTool.Rules.Partner.zip contains a starting project for new rules. Unpack CodeUpgradeTool.Rules.Partner.zip, remove write protection from files and open the Visual Studio project inside. By the way, I used Visual Studio 2013 to build the following example.

The project contains two classes: TestRuleSweeper (for rules that only looks for patterns) and TestRuleMutator (for rules that can actually fix identified patterns). I doubt I could write a completely generic fix for wrong updates of InventDim, therefore I’ll create a sweeper (and delete the mutator). This is TestRuleSweeper.cs:

namespace Microsoft.Dynamics.AX.Tools.CodeUpgradeTool.Rules.Partner.PartnerRules
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using Microsoft.Dynamics.AX.Framework.Xlnt.XppParser;
    using Microsoft.Dynamics.AX.Framework.Xlnt.XppParser.Pass2;
 
    [Author("")] 
    [Description("PartnerRuleSweeperTest")]
    [AutomaticallyFixable("No")]
    [Category("Test")]
    [Version("2009 -> 2012")]
    public class TestRuleSweeper : PipelineableSweeper
    {
        public TestRuleSweeper(IDiagnosticSink diagnostics, IMetadataProvider metadataProvider, IDictionary<string, string> parameters)
          : base(diagnostics, metadataProvider, parameters)
        {         
        }
 
        ////TODO: override the method from ASTSweeper that fits the rule
    }
}

We can go and override an appropriate “visit” method, such as VisitQualifiedCall():

protected override object VisitQualifiedCall(object payload, QualifiedCall qualifiedCall)
{
    return base.VisitQualifiedCall(payload, qualifiedCall);
}

Let’s not continue with this implementation, because it wouldn’t work anyway. Instead, we’ll update the project to work with the new version.

First of all, remove these assemblies from project references:

  • Microsoft.Dynamics.AX.Framework.Xlnt.XppParser
  • Microsoft.Dynamics.AX.Framework.Xlnt.XppParser.Pass2

and add a few new ones:

  • Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Parser
  • Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Parser.Pass2
  • Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Rules

You’ll find them in AX client bin folder (such as c:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\).

References

Now a few things stop working, because they’ve changed in newer versions. We have to update them.

Firstly, remove old using statements and use new namespaces instead:

using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Parser;
using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Parser.Pass2;
using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Rules.ObjectModel;

Then throw away old attributes, such as [Description], and replace them with new ones, such as [RuleDescription] (you’ll find an example below).

The next thing you’ll notice is a compilation error related to the base class, PipelineableSweeper. Ignore the message about generic arguments; the fact is that even sweepers now extends the PipelineableMutator class. Change the class accordingly.

This is how the TestRuleSweeper template should look like these days:

namespace Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Rules.Partner.PartnerRules
{
    using System.Collections.Generic;
    using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Parser;
    using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Parser.Pass2;
    using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Rules.ObjectModel;
 
    [RuleCategory("")]
    [RuleTargetVersion("")]
    [RuleAuthor("")]
    [RuleDescription("")]
    [RuleType(AutomaticallyFixable.No)]
    public class TestRuleSweeper : PipelineableMutator
    {
        public TestRuleSweeper(IDiagnosticSink diagnostics, IMetadataProvider metadataProvider, IDictionary<string, string> parameters)
          : base(diagnostics, metadataProvider, parameters)
        {       
        }
    }
}

If you copy VisitQualifiedCall() from our previous implementation and compile the solution, you’ll get another error saying that VisitQualifiedCall() must returns Evaluation. Simply change the return type from object to Evaluation.

The remaining thing is to actually implement VisitQualifiedCall(). It’s also time to rename the class and fill values to the attributes. You may also want to change the namespace and the assembly name.

This is my complete class:

namespace Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Rules.Goshoom
{
    using System.Collections.Generic;
    using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Parser;
    using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Parser.Pass2;
    using Microsoft.Dynamics.AX.Framework.Tools.CodeUpgradeTool.Rules.ObjectModel;
 
    [RuleCategory("Inventory")]
    [RuleTargetVersion("2012")]
    [RuleAuthor("Martin Dráb")]
    [RuleDescription("InventDim.update() / doUpdate() shoukd not be updated by directly. InventDim::findOrCreate() should be used instead.")]
    [RuleType(AutomaticallyFixable.No)]
    public class InventDimUpdateSweeper : PipelineableMutator
    {
        public InventDimUpdateSweeper(IDiagnosticSink diagnostics, IMetadataProvider metadataProvider, IDictionary<string, string> parameters)
          : base(diagnostics, metadataProvider, parameters)
        {       
        }
 
        protected override Evaluation VisitQualifiedCall(object payload, QualifiedCall qualifiedCall)
        {
            Guard.ValidateIsNotNull(qualifiedCall, @"qualifiedCall");
 
            base.VisitQualifiedCall(payload, qualifiedCall);
            var qualifierType = qualifiedCall.Qualifier.GetExpressionType();
 
            if ((qualifiedCall.MethodName.Compare("doUpdate") || qualifiedCall.MethodName.Compare("update"))
                && (qualifierType != null && qualifierType.ToString().Compare("InventDim")))
            {
                this.AddDiagnosticWarning(1, qualifiedCall.Position, "Do not update InventDim table directly. Use InventDim::findOrCreate() instead.");
            }
 
            return qualifiedCall;
        }
    }
}

I don’t want to spend much time exploring the implementation of VisitQualifiedCall(), just notice how easy is to check whether the type of variable is InventDim. It’s because the compiler understands the code and makes this information available to Code Upgrade Tool as well.

It’s time to deploy our new rule. Build the solution, right-click the project and choose Open Folder in File Explorer. Open Debug\bin or Release\bin (depending on which configuration you used), find the DLL file with your rule and copy it to AX client bin folder.

Assembly

Now you can load the rule in exactly the same way as how you loaded Microsoft rules (return to the top of this post if you’re not sure how). Metadata such as author and description are copied from attributes of the class:

And you’re done! Now simply run conflict detection with Create code upgrade patterns project ticked let AX to do the rest for you.

UpgradeProject

One disadvantage of my implementation is that you won’t see actual place in code where to find the pattern. Microsoft rules place TODO comments to code to make it obvious and they have a nice utility class for it, unfortunately it’s an internal class and therefore it would take more effort to achieve the same thing.

Although this post was mainly about infrastructure and not about all the fancy rules you could write, I hope you see the potential by yourself. It may be really useful, whether you want to automatically change code during upgrade, find some patterns in your application or maybe something completely different.

Unfortunately I also have one bad news. When testing my new rule, I found it’s ignored in quite a few cases. You can find my analysis and some suggestions in the follow-up blog post: Objects ignored by Code Upgrade Tool.

2 Comments

  1. Hi Martin,
    I can’t find and download the code upgrade tool; i search it on LCS downloadable tool and on InformationSource.
    Could you help me please?
    Thank’s
    Enrico.

Leave a Reply

Your email address will not be published.