CodeTag 1.1.1
dotnet add package CodeTag --version 1.1.1
NuGet\Install-Package CodeTag -Version 1.1.1
<PackageReference Include="CodeTag" Version="1.1.1" />
<PackageVersion Include="CodeTag" Version="1.1.1" />
<PackageReference Include="CodeTag" />
paket add CodeTag --version 1.1.1
#r "nuget: CodeTag, 1.1.1"
#:package CodeTag@1.1.1
#addin nuget:?package=CodeTag&version=1.1.1
#tool nuget:?package=CodeTag&version=1.1.1
CodeTag Analyzer
CodeTag is a Roslyn-based analyzer that allows developers to enforce the use of specific tags on their code, thereby allowing developers to trace references throughout a codebase.
ObsoleteAttribute propagates through a codebase by following references, issuing Warnings or Errors until you either remove the reference or tag the containing method or property with ObsoleteAttribute.
CodeTag does the same thing. Apply DefineCodeTagAttribute to a property, method, or constructor, and anything which uses it will require a CodeTagAttribute with a string identifier to match it. If you do not provide a string identifier value to the DefineCodeTagAttribute it will attempt to generate something plausible from the code structure.
The original use case is that I needed a way to ensure that any code that references (no matter how indirectly) an EF navigation property was distinctively marked, so I could make sure it was eagerly loaded correctly.
Features
CodeTag Attributes
CodeTag.DefineCodeTagAttributemarks a method, property, or constructor as "tagged". Optionally provide astringidentifier to the constructor to use a custom identifier.CodeTag.CodeTagAttributeacknowledges a method, property, or constructor as "tagged" and deactivates diagnostics related to this particular tag identifier for this element.CodeTag.EnableCodeTagAttributemarks a class or struct and its properties, and its nested classes and properties, for scanning by the Analyzer. Can also be applied to individual properties and methods. Note that theDefineCodeTagAttributedoes not have to be within a class marked withEnableCodeTagAttribute.
CodeTag Analyzer
- Analyzer that checks your code for the correct use of CodeTags and provides diagnostic feedback.
Getting Started
Install the NuGet package to your C# project using your package management process of choice
Usage
Simple Tagging
In CodeTag's most basic use-case, you can tag methods, properties, or constructors to trace their usage across your codebase. Let's dive into a few examples:
Default Tag Generation:
using CodeTag.Common;
namespace Test
{
[EnableCodeTag]
public class Beta
{
[DefineCodeTag] // This will generate a default tag
public Gamma Charlie { get; set; } = default!;
[CodeTag("Test.Beta.Charlie")] // Using the generated tag
public void Bar()
{
Charlie.Foo();
}
}
public class Gamma
{
public void Foo() { }
}
}
- Class
Betais annotated with theEnableCodeTagattribute. This means properties and methods inBetaare required to useCodeTagattributes as appropriate. - Property
Beta.Charlieis tagged with theDefineCodeTagattribute, generating the default identifierTest.Beta.Charlie- Default identifiers are generated from the namespace, enclosing types, and element name.
- Method
Beta.Bar()has a reference toCharlie, so must be tagged with a matchingCodeTag.Beta.Bar()has[CodeTag("Test.Beta.Charlie")]so it will not issue an error.
Methods can also be tagged using the DefineCodeTag attribute. Once tagged, any other method, property, or constructor that references the tagged method requires its own CodeTag attribute with the matching identifier.
using CodeTag.Common;
namespace Test
{
[EnableCodeTag]
public class Beta
{
// Tagging a method with default identifier generation
[DefineCodeTag]
public int Foo()
{
return 1;
}
// Referencing the tagged method requires a matching CodeTag
[CodeTag("Test.Beta.Foo")]
public int Bar()
{
return Foo();
}
// Properties that use tagged methods and properties also require matching CodeTag attributes
[CodeTag("Test.Beta.Foo")]
public int Alice => Bar();
}
}
- The class
Betais annotated withEnableCodeTag. The properties and methods are required to useCodeTagattributes if they directly or indirectly reference any element which has aDefineCodeTagattribute. - The method
Beta.Foo()is tagged using theDefineCodeTagattribute. Since there is no string argument provided, it generates the identifier for this tag from the method name, enclosing classes, and namespace, resulting inTest.Beta.Foo. - The method
Beta.Bar()referencesBeta.Foo(), and as a result, requires its ownCodeTagattribute with the identifierTest.Beta.Foo. - The property
Beta.AlicereferencesBeta.Bar(), and as a result, also requires its ownCodeTagattribute with the identifierTest.Beta.Foo, becauseBeta.Bar()referencesBeta.Foo().
Custom Tag Identifier:
using CodeTag.Common;
namespace Test
{
[EnableCodeTag]
public class Beta
{
[DefineCodeTag("Beep!")] // Custom tag identifier
public Gamma Charlie { get; set; } = default!;
[CodeTag("Beep!")] // Using the custom tag
public void Bar()
{
Charlie.Foo();
}
}
public class Gamma
{
public void Foo() { }
}
}
- Class
Betahas anEnableCodeTagattribute, and so if its properties or methods contain director or indirect references to elements withDefineCodeTagattribute, they must haveCodeTagattributes. - The
DefineCodeTagattribute accepts a customstringas identifiers. Beta.Charlieuses the custom tagBeep!.- So
Beta.Bar()also requires the custom tagBeep!.
Stacking Tags
When a method, property, or constructor references multiple tagged elements, it may be required to stack multiple CodeTag attributes to address each individual tag. This ensures that all dependencies of a function are properly tracked.
using CodeTag.Common;
namespace Test
{
[EnableCodeTag]
public class Alpha
{
[DefineCodeTag("Beep!")] // Custom tag for Brian property
public Beta Brian { get; set; } = default!;
// Applying multiple tags
[CodeTag("Beep!")]
[CodeTag("Test.Gamma.Foo")]
[CodeTag("Test.Beta.Charlie")]
public void Baz()
{
Brian.Bar(); // This method references both "Test.Beta.Charlie" and "Test.Gamma.Foo"
}
}
[EnableCodeTag]
public class Beta
{
[DefineCodeTag] // Default tag for Charlie property
public Gamma Charlie { get; set; } = default!;
// Stacking required tags
[CodeTag("Test.Beta.Charlie")]
[CodeTag("Test.Gamma.Foo")]
public void Bar()
{
Charlie.Foo(); // This method references a tagged element
}
}
public class Gamma
{
[DefineCodeTag] // Default tag for Foo method
public int Foo() { return 5; }
}
}
- Classes
AlphaandBetahaveEnableCodeTagattributes. Their properties and methods are required to useCodeTagattributes if they directly or indirectly reference any element with aDefineCodeTagattribute. - Class
Gammadoes not have anEnableCodeTagattribute. Properties and methods in this class are not required to haveCodeTagattributes.- Note that
Beta.Bar()is required to have aCodeTagwhich references theDefineCodeTagonGamma.Foo(), despiteGammanot having anEnableCodeTagattribute. You can annotate any method or property withDefineCodeTag, but methods, properties, and classes/structs only requireCodeTagif they have theEnableCodeTagattribute. - Similarly, property
Gamma.Quxdoes not issue an error because it is not in anEnableCodeTagcontext.
- Note that
- Method
Alpha.Baz()referencesBeta.Bar(), which in turn referencesGamma.Foo(). - Each of these methods and properties has their own tags, and due to this hierarchy of calls, the method
Alpha.Baz()requires all three tags (Beep!,Test.Gamma.Foo, andTest.Beta.Charlie). - This demonstrates the ability to stack multiple
CodeTagattributes on a single element to address all its references.
Remember, stacking is essential when a single method, property, or constructor interacts with multiple tagged elements. It ensures that all related tags are acknowledged, keeping your codebase traceable.
Diagnostic Errors
CT001 CodeTag compliance
A method or property with the EnableCodeTag attribute, or within a class or struct with an EnableCodeTag attribute, must, if it references an element which is tagged with a CodeTag, use a CodeTag attribute with a matching identifier. If it does not, CT001 will be issued.
CT001 will also be issued if there are duplicate CodeTag attributes on a single element within an EnableCodeTag context.
CT001 will also be issued if there are unnecessary (unreferenced) CodeTag attributes on an element within an EnableCodeTag context.
using CodeTag.Common;
namespace Test
{
[EnableCodeTag]
public class Beta
{
[DefineCodeTag] // This will generate a default tag
public Gamma Charlie { get; set; } = default!;
// This is missing a CodeTag attribute.
// CT001
public void Bar()
{
Charlie.Foo();
}
}
public class Gamma
{
public void Foo() { }
}
}
To resolve this Error, add the missing CodeTag or remove it from the referenced elements.
The analyzer will detect and highlight instances where a CodeTag attribute is used, but the corresponding reference that necessitates the tag is absent.
using CodeTag.Common;
namespace Test
{
[EnableCodeTag]
public class Beta
{
public Gamma Charlie { get; set; } = default!;
// This CodeTag is unnecessary because there's no reference to a tagged element.
// CT001
[CodeTag("Test.Beta.Charlie")]
public void Bar()
{
Charlie.Foo();
}
}
public class Gamma
{
public void Foo() { }
}
}
Beta.Bar()has been tagged with the CodeTagTest.Beta.Charlie, butBeta.Charlieisn't tagged with aDefineCodeTagattribute.- The analyzer will issue a Warning-level diagnostic
CT002 (Unnecessary CodeTag)
To resolve this warning, you have a couple of options:
- Remove the unnecessary CodeTag attribute.
public void Bar()
{
Charlie.Foo();
}
- If the intent was to tag Charlie, then add a
DefineCodeTagattribute
[DefineCodeTag]
public Gamma Charlie { get; set; } = default!;
By addressing unnecessary tags, you ensure that your code tagging system remains relevant, concise, and meaningful.
Applying the same CodeTag more than once to a single code element is redundant and can lead to confusion. The analyzer detects and reports these redundancies to help maintain the clarity of your code tagging system.
Copy code
using CodeTag.Common;
namespace Test
{
[EnableCodeTag]
public class Beta
{
[DefineCodeTag]
public Gamma Charlie { get; set; } = default!;
// Applying the same CodeTag twice is redundant
// CT001
[CodeTag("Test.Beta.Charlie")]
[CodeTag("Test.Beta.Charlie")]
public void Bar()
{
Charlie.Foo();
}
}
public class Gamma
{
public void Foo() { }
}
}
Beta.Bar()referencesBeta.Charlieand is therefore tagged with the correspondingCodeTag.- The
CodeTagattribute has been applied twice with the same identifier, which is unnecessary. - Such code will be flagged with an Error-level diagnostic
CT003.
To resolve this, simply remove the redundant CodeTag attribute:
[CodeTag("Test.Beta.Charlie")]
public void Bar()
{
Charlie.Foo();
}
By ensuring that each code element has unique tags, you can maintain a clean and effective code tagging system.
Viewing Diagnostics
Once the tags are in place, the analyzer will automatically check your code in the background. If there are any issues, they will be displayed in the Error List window of your IDE. Issues will be flagged with appropriate markings (red squiggles).
License
This project is licensed under the MIT License - see the LICENSE.md file for details.
Changelog
| Major | Minor | Patch | Date | Notes |
|---|---|---|---|---|
| 1 | 1 | 1 | 09/13/2023 | Fixed "Syntax Tree is not part of the Compilation" exception |
| 1 | 1 | 0 | 09/11/2023 | Added EnableCodeTag, consolidated errors to CT001, removed CodeFix pending rethink |
| 0 | 12 | 0 | 09/03/2023 | License |
| 0 | 11 | 0 | 09/03/2023 | Readme |
| 0 | 10 | 0 | 09/03/2023 | Performance improvements |
| 0 | 9 | 0 | 09/03/2023 | CT004 Invalid CodeTag |
| 0 | 8 | 0 | 09/03/2023 | Tests for CodeFix for CT003 |
| 0 | 7 | 0 | 09/02/2023 | CodeFix for CT003 |
| 0 | 6 | 0 | 09/02/2023 | Tests for CT003 |
| 0 | 5 | 0 | 09/02/2023 | CT003 Duplicate CodeTag |
| 0 | 4 | 0 | 09/02/2023 | Tests for CT002 |
| 0 | 3 | 0 | 09/01/2023 | CT002 Unnecessary CodeTag |
| 0 | 2 | 0 | 09/01/2023 | Tests for CT001 |
| 0 | 1 | 0 | 09/01/2023 | CT001 Missing CodeTag |
Acknowledgments
For my team, The Specials, who put up with me
(in reverse alphabetical order)
- Saranya Theppala
- Mitch Thompson
- Mary Remo
- Leith Abudiab
- Harry Thomas Kibby IV
- Haroon Iqbal
- Carter Hill
- Becky Curnow
- Ashley Lee
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.Analyzers (>= 2.9.8)
- Microsoft.CodeAnalysis.CSharp (>= 3.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.