A place for spare thoughts

23/08/2011

Checking correctness of DependencyProperty declarations

Filed under: wpf — Ivan Danilov @ 19:44

DependencyProperty declaration is a fairly standard action and it is often performed with some kind of templates or snippets. Name in DependencyProperty.Register call should match property name itself, some methods should be declared, it should be most times static and readonly etc. Also if you declare read-only DP – you want Key to be non-public and DP itself to be public (otherwise why to declare it as read-only in first place?)

So, simple rule to check some of these things is below. It is almost trivial to plug it in SourceChecker I described before (actually we have this rule there).

public class AttachedPropertiesNamedCorrectlyRule
{
    private static readonly Regex PropertyPattern = new Regex(
        @"(?public\s+)?(?(static\s+)?(readonly\s+)?(static\s+)?)" + // match any order/presence of modifiers
        @"DependencyProperty\s+(?\w+)\s*" + // propname is identifier name, should be XxxProperty
        @"(" + // match registration assignment, assignment from property key or just ';' here
        @"   (?" +
        @"      =\s*DependencyProperty\.Register(Attached)?\s*\(\s*" + // match attached properties also
        @"      ""(?[^""]+)""" +
        @"   )" +
        @"|" +
        @"   (?" +
        @"      =\s*(?\w+)\.DependencyProperty\s*;" +
        @"   )" +
        @"|" +
        @"   (?;)" +
        @")",
        RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.ExplicitCapture);

    private static readonly Regex PropertyKeyPattern = new Regex(
        @"(?public\s+)?(?(static\s+)?(readonly\s+)?(static\s+)?)" + // match any order/presence of modifiers
        @"DependencyPropertyKey\s+(?\w+)\s*" + // propname is identifier name, should be XxxProperty
        @"(" + // match assignment or just ';' here
        @"   (?" +
        @"      =\s*DependencyProperty\.Register(Attached)?ReadOnly\s*\(\s*" + // match attached keys also
        @"      ""(?[^""]+)""" +
        @"   )" +
        @"|" +
        @"   (?;)" +
        @")",
        RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.ExplicitCapture);

    public IEnumerable Check(string cscode)
    {
        var errors = new List<string>();

        CheckDependencyProperties(cscode, errors);
        CheckDependencyPropertyKeys(cscode, errors);

        return errors;
    }

    private static void CheckDependencyProperties(string sourceText, List<string> errors)
    {
        Match matchResults = PropertyPattern.Match(sourceText);
        while (matchResults.Success)
        {
            var propname = matchResults.Groups["propname"].Value;
            var modifiers = matchResults.Groups["modifiers"].Value;
            bool isPublic = matchResults.Groups["publicity"].Success;
            bool noInitializer = matchResults.Groups["noInitializer"].Success;
            bool initializedFromKey = matchResults.Groups["assignmentFromKey"].Success;
            bool isStatic = modifiers.Contains("static");
            bool isReadonly = modifiers.Contains("readonly");

            if (!isPublic)
            {
                // probably some internal usage passed in instance constructor, can't say if it is wrong
                matchResults = matchResults.NextMatch();
                continue;
            }

            if (!isStatic)
                errors.Add(string.Format("DependencyProperty {0} is not marked as static", propname));
            if (!isReadonly)
                errors.Add(string.Format("DependencyProperty {0} is not marked as readonly", propname));

            // Initialization in static constructor should be avoided because of performance impact of static ctor presence
            // See here: http://stackoverflow.com/questions/2921828/static-constructors-cause-a-performance-over-head
            // Also SourceChecker can't check such initialization reliably (for example with partial classes static ctor 
            // could be in other file) and ensure that property name matches string given as parameter
            if (noInitializer)
            {
                errors.Add(string.Format("DependencyProperty {0} is not initialized or initialized in static constructor.", propname));
            }
            else if (initializedFromKey)
            {
                var keyName = matchResults.Groups["keyName"].Value;
                var expectedKeyName = propname + "Key";
                if (keyName != expectedKeyName)
                    errors.Add(string.Format("DependencyProperty {0} either named or initialized incorrectly (key name is '{1}', expected '{2}')", propname, keyName, expectedKeyName));
            }
            else
            {
                var stringPropname = matchResults.Groups["stringPropname"].Value;
                var expected = stringPropname + "Property";
                if (propname != expected)
                    errors.Add(string.Format("DependencyProperty {0} either named or initialized incorrectly (param is '{1}', name should be {2})", propname, stringPropname, expected));
            }

            matchResults = matchResults.NextMatch();
        }
    }

    private static void CheckDependencyPropertyKeys(string sourceText, List<string> errors)
    {
        Match matchResults = PropertyKeyPattern.Match(sourceText);
        while (matchResults.Success)
        {
            var keyname = matchResults.Groups["propname"].Value;
            var modifiers = matchResults.Groups["modifiers"].Value;
            bool isPublic = matchResults.Groups["publicity"].Success;
            bool noInitializer = matchResults.Groups["noInitializer"].Success;
            bool isStatic = modifiers.Contains("static");
            bool isReadonly = modifiers.Contains("readonly");

            if (isPublic)
                errors.Add(string.Format("DependencyPropertyKey {0} is marked as public. Either make it more restricted or consider making property read-write.", keyname));
            if (!isStatic)
                errors.Add(string.Format("DependencyPropertyKey {0} is not marked as static", keyname));
            if (!isReadonly)
                errors.Add(string.Format("DependencyPropertyKey {0} is not marked as readonly", keyname));

            // Initialization in static constructor should be avoided because of performance impact of static ctor presence
            // See here: http://stackoverflow.com/questions/2921828/static-constructors-cause-a-performance-over-head
            // Also SourceChecker can't check such initialization reliably (for example with partial classes static ctor 
            // could be in other file) and ensure that property name matches string given as parameter
            if (noInitializer)
            {
                errors.Add(string.Format("DependencyPropertyKey {0} is not initialized or initialized in static constructor.", keyname));
            }
            else
            {
                var stringPropname = matchResults.Groups["stringPropname"].Value;

                var expected = stringPropname + "PropertyKey";
                if (keyname != expected)
                    errors.Add(string.Format("DependencyPropertyKey {0} either named or initialized incorrectly (param is '{1}', name should be {2})", keyname, stringPropname, expected));
            }

            matchResults = matchResults.NextMatch();
        }
    }
}
Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: