Enforcing naming conventions
In any professional team, it is essential that everyone uses the same terminology. This is especially true in large codebases maintained by multiple people over a long period.
To ensure consistency, Metalama provides a simple way to enforce naming conventions across lines of inheritance. For example, you can easily require that any class inheriting the IFactory
interface has a name that ends with the Factory
prefix. This helps to ensure that everyone is using the same language and understanding the same concepts.
Enforcing naming conventions using custom attributes
If you want to enforce a naming convention for any type that derives from a given class or interface, when you own the source code of this class or interface, the easiest approach is to use a custom attribute. Follow these steps.
Add the
Metalama.Extensions.Architecture
package to your project.Add the DerivedTypesMustRespectNamingConventionAttribute custom attribute to the base class or interface. The argument of this custom attribute must be the naming pattern, where the asterisk (
*
) will match any substring.
Note
If you want full control over the regular expression, use DerivedTypesMustRespectRegexNamingConventionAttribute.
Example
In the following example, we require all types implementing IFactory
to have a name that ends with the Factory
suffix.
1using Metalama.Extensions.Architecture.Aspects;
2
3namespace Doc.Architecture.NamingConvention
4{
5 [DerivedTypesMustRespectNamingConvention("*Factory")]
6 public interface IFactory { }
7
8 // This will report a warning because the naming convention is not respected.
Warning LAMA0903: The type 'ThingCreator' does not respect the naming convention set on the base class or interface 'IFactory'. The type name should match the "^.*Factory$" pattern.
9 internal class ThingCreator : IFactory { }
10
11 // This is properly named.
12 internal class WidgetFactory : IFactory { }
13}
14
1using Metalama.Extensions.Architecture.Aspects;
2
3namespace Doc.Architecture.NamingConvention
4{
5 [DerivedTypesMustRespectNamingConvention("*Factory")]
6 public interface IFactory { }
7
8 // This will report a warning because the naming convention is not respected.
9 internal class ThingCreator : IFactory { }
10
11 // This is properly named.
12 internal class WidgetFactory : IFactory { }
13}
14
Enforcing naming conventions using fabrics
When you want to enforce naming conventions for a different scenario than the one above, you cannot use custom attributes. Instead, you need to use fabrics and write compile-time code. Follow these steps:
Add the
Metalama.Extensions.Architecture
package to your project.Create or reuse a fabric type as described in Using fabrics.
Import the Metalama.Extensions.Architecture.Fabrics namespace to benefit from extension methods.
Edit the AmendProject, AmendNamespace or AmendType of this method. Open the dance by calling amender.Verify().
Select the APIs using the Select, SelectMany and Where methods. You may also find the SelectTypesDerivedFrom method useful.
Call the MustRespectNamingConvention method.
Note
Unlike DerivedTypesMustRespectNamingConventionAttribute, the naming convention added by the MustRespectNamingConvention is neither inherited nor cross-project. If you want the naming convention to apply to several projects, use the techniques described in Adding aspects to many aspects.
Example: Enforcing a naming convention on all types derived from a given system type
Many teams require UI pages to be suffixed Page
, controls Control
, and so on. This cannot be achieved using a custom attribute because you don't own the source code of the base class. In the following example, we show how to implement this requirement: we require all classes derived from TextReader
to be suffixed Reader
. We use the SelectTypesDerivedFrom method to select the relevant types.
1using Metalama.Extensions.Architecture.Fabrics;
2using Metalama.Framework.Fabrics;
3using System.IO;
4
5namespace Doc.Architecture.NamingConvention_Fabric
6{
7 internal class Fabric : ProjectFabric
8 {
9 public override void AmendProject( IProjectAmender amender )
10 {
11 amender.Verify().SelectTypesDerivedFrom( typeof( TextReader ) ).MustRespectNamingConvention( "*Reader" );
12 }
13 }
14
15 // The naming convention is broken.
Warning LAMA0906: The type 'TextLoader' does not respect the naming convention set by a fabric. The type name should match the "^.*Reader$" pattern.
16 internal class TextLoader : TextReader { }
17}
18
1using Metalama.Extensions.Architecture.Fabrics;
2using Metalama.Framework.Fabrics;
3using System.IO;
4
5namespace Doc.Architecture.NamingConvention_Fabric
6{
7
8#pragma warning disable CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
9 internal class Fabric : ProjectFabric
10 {
11 public override void AmendProject(IProjectAmender amender) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
12
13 }
14
15#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
16
17
18 // The naming convention is broken.
19 internal class TextLoader : TextReader { }
20}
21