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" />
paket add CodeTag --version 1.1.1
#r "nuget: CodeTag, 1.1.1"
// Install CodeTag as a Cake Addin #addin nuget:?package=CodeTag&version=1.1.1 // Install CodeTag as a Cake Tool #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.DefineCodeTagAttribute
marks a method, property, or constructor as "tagged". Optionally provide astring
identifier to the constructor to use a custom identifier.CodeTag.CodeTagAttribute
acknowledges a method, property, or constructor as "tagged" and deactivates diagnostics related to this particular tag identifier for this element.CodeTag.EnableCodeTagAttribute
marks 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 theDefineCodeTagAttribute
does 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
Beta
is annotated with theEnableCodeTag
attribute. This means properties and methods inBeta
are required to useCodeTag
attributes as appropriate. - Property
Beta.Charlie
is tagged with theDefineCodeTag
attribute, 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
Beta
is annotated withEnableCodeTag
. The properties and methods are required to useCodeTag
attributes if they directly or indirectly reference any element which has aDefineCodeTag
attribute. - The method
Beta.Foo()
is tagged using theDefineCodeTag
attribute. 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 ownCodeTag
attribute with the identifierTest.Beta.Foo
. - The property
Beta.Alice
referencesBeta.Bar()
, and as a result, also requires its ownCodeTag
attribute 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
Beta
has anEnableCodeTag
attribute, and so if its properties or methods contain director or indirect references to elements withDefineCodeTag
attribute, they must haveCodeTag
attributes. - The
DefineCodeTag
attribute accepts a customstring
as identifiers. Beta.Charlie
uses 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
Alpha
andBeta
haveEnableCodeTag
attributes. Their properties and methods are required to useCodeTag
attributes if they directly or indirectly reference any element with aDefineCodeTag
attribute. - Class
Gamma
does not have anEnableCodeTag
attribute. Properties and methods in this class are not required to haveCodeTag
attributes.- Note that
Beta.Bar()
is required to have aCodeTag
which references theDefineCodeTag
onGamma.Foo()
, despiteGamma
not having anEnableCodeTag
attribute. You can annotate any method or property withDefineCodeTag
, but methods, properties, and classes/structs only requireCodeTag
if they have theEnableCodeTag
attribute. - Similarly, property
Gamma.Qux
does not issue an error because it is not in anEnableCodeTag
context.
- 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
CodeTag
attributes 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.Charlie
isn't tagged with aDefineCodeTag
attribute.- 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
DefineCodeTag
attribute
[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.Charlie
and is therefore tagged with the correspondingCodeTag
.- The
CodeTag
attribute 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. |
.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.