C# Code Management
This article explains how to control Code Management (Code Weaving) behaviour for C# files when "RoslynWeaver" (the C# code management extension of the Intent.OutputManager.RoslynWeaver
Module) is used.
Overview of how it works
The RoslynWeaver parses C# files into an abstract syntax tree and applies code management logic on a node-by-node basis. An individual node on the abstract syntax tree is referred to as a syntax node. Syntax nodes may have one or more children which are also syntax nodes.
Example of an abstract syntax tree of a C# file.
The RoslynWeaver compares the generated content from the template with the existing file (if there is one) on a node-by-node basis. Instructions in the form of C# attributes are used by the RoslynWeaver for it to determine for a particular syntax node what content it should ignore, replace with content generated by the template or perhaps remove entirely.
Code management instructions
Instructing the RoslynWeaver on how to treat particular syntax nodes is done using code management instructions in your source code, such as e.g. [IntentManaged(Mode.Ignore)]
, [IntentIgnore]
.
Management modes
Both the [IntentManaged(…)]
and [DefaultIntentManaged(…)]
attributes have a mandatory parameter which accepts a Mode
enum value of one of the following:
Enum Value | Description |
---|---|
Fully |
Intent has full control over the particular syntax node, any deviations in the existing file's syntax node are overwritten with the content generated by the template. Descendant syntax nodes can be opted-out of being fully managed having an [IntentManaged(…)] attribute applied to them. |
Merge |
Intent will add and remove Intent generated code for the the syntax node but will never remove code which was manually added¹. |
Ignore |
Intent must ignore this syntax node and not remove or overwrite it with content generated by the template. Code management instructions on descendant syntax nodes are likewise ignored, i.e. it is not possible to opt-out of being ignored as a descendant. |
[1] Prior to version 4.7.0 of the Intent.OutputManager.RoslynWeaver
module, it was not possible for it to identify what was previously generated by Intent so it would never delete anything when in merge mode.
The [DefaultIntentManaged(…)]
attribute
The [DefaultIntentManaged(…)]
attribute can be used to set the "default" (or "fallback") management mode for syntax nodes which otherwise have no code management instructions of their own.
A [DefaultIntentManaged(<mode>)]
assembly attribute without additional options must be specified at least once at the top of the file and can also be applied any additional number of times with any combination of additional options either as assembly attributes at the top of the file or to type declaration syntax nodes (classes, enums, interfaces, etc), for example:
[assembly: DefaultIntentManaged(Mode.Fully)]
[assembly: DefaultIntentManaged(Mode.Fully, Targets = Targets.Usings)]
[DefaultIntentManaged(Mode.Fully, Body = Mode.Ignore, Targets = Targets.Constructors | Targets.Methods)]
public class Class
{
}
DefaultIntentManaged Targets
property
The [DefaultIntentManaged(…)]
attribute's Targets
property can be set to have a value of one or more Targets
enum flags to specify that the instruction should only "target" particular syntax node types. To specify multiple target syntax node types, use the bitwise logical OR operator |
, e.g. Targets = Targets.Constructors | Targets.Methods
will make the instruction apply to both Constructors and Methods.
The following Targets
are available:
Classes
Constructors
Delegates
EnumMembers
Enums
Fields
Interfaces
Methods
Namespaces
OperatorConversions
Operators
Properties
Records
Structs
TopLevelStatements
Usings
The [IntentManaged(…)]
attribute
Type declaration syntax nodes (e.g. classes, enums, interfaces, etc) and type member declaration syntax nodes (e.g. fields, methods, properties, etc) can have an [IntentManaged(…)]
attribute applied to them to control their code management behaviour.
[IntentManaged(…)]
when applied to type member declaration syntax nodes
The [IntentManaged(…)]
attribute can be applied to type member declaration syntax nodes (e.g. fields, methods, properties, etc) to control the behaviour of that member in particular, for example to have the code management ignore a method which you made manual changes to, you can add an [IntentManaged(Mode.Ignore)]
attribute to it like so:
[IntentManaged(Mode.Ignore)]
public void ChangeCountry(string country)
{
throw new NotImplementedException();
}
When the RoslynWeaver sees this, it will know not to modify (or remove) this method in any way during code merging.
[IntentManaged(…)]
when applied to type declaration syntax nodes
The [IntentManaged(…)]
attribute can be applied to type declaration syntax nodes (e.g. classes, enums, interfaces, etc) to control the following:
- Signature: Controls access modifiers, generic type parameters and what is being derived from and/or implemented.
- Body: Controls whether or not members (e.g. fields, methods, properties, etc) of the class should be added/removed (it does not control the content of the members):
- Fully: Members are added to/removed from the existing file to ensure they match the template output.
- Merge: Any members from the template output which are missing from the existing file will be added, additional members which were previously added by Intent will be removed¹ and any additional members which where manually user added will not be removed.
- Ignore: No members will be added to or removed from the existing file.
- Comments: Controls comments of the type declaration, falls back to
Signature
behaviour if unspecified. - Attributes: Controls attributes of the type declaration, falls back to
Signature
behaviour if unspecified.
When a type declaration syntax node has [IntentManaged(Mode.Ignored)]
applied to it, descendant nodes (i.e. members, such as fields, properties, etc.) all become ignored and will stop being updated or removed. Additionally, code management instructions on descendant nodes are also ignored and disregarded meaning it is not possible to "opt-out" of being ignored as a descendant.
¹ Prior to version 4.7.0 of the Intent.OutputManager.RoslynWeaver
module, it was not possible for it to identify what was previously generated by Intent so it would never delete anything when in merge mode.
Code management attribute properties
The [IntentManaged(…)]
and [DefaultIntentManaged(…)]
attributes have additional properties which can provide finer grained control of code management for a particular syntax node. By default each of these properties has the same Mode
as the default parameter of the attribute, so:
[IntentManaged(Mode.Fully, Body = Mode.Fully, Signature = Mode.Fully, Comments = Mode.Fully, Attributes = Mode.Fully)]
is equivalent to:
[IntentManaged(Mode.Fully)]
The following table documents the available attribute properties and how the RoslynWeaver interprets them when applied to different syntax node types:
Syntax node type | Attribute property | Description |
---|---|---|
(All) | Attributes | By default the Signature parameter determines this parameter's setting but this parameter instructs the RoslynWeaver to treat the syntax node's Attribute differently. |
(All) | Comments | By default the Signature parameter determines this parameter's setting but this parameter instructs the RoslynWeaver to treat the syntax node's Comments differently. |
Class | Signature | Instructs the RoslynWeaver to treat the definition of a Class (class name, inheritance, etc.) differently to the default parameter setting. |
Class | Body | Instructs the RoslynWeaver to treat the members of a Class (methods, properties, etc.) differently to the default parameter setting. |
Constructor, Method | Signature | Instructs the RoslynWeaver to treat the definition of a Method (method name, parameters, return type, etc.) differently to the default parameter setting. |
Constructor, Method | Body | Instructs the RoslynWeaver to treat the implementation part of a method (where the code goes) differently to the default parameter setting. |
Field, Property | Signature | Instructs the RoslynWeaver to treat the definition of a Field (field name, type, etc.) differently to the default parameter setting. |
Field, Property | Body | Instructs the RoslynWeaver to treat the value differently to the default parameter setting. |
Shorthand attributes
Note
Shorthand attributes were introduced in version 4.2.0
of the Intent.OutputManager.RoslynWeaver
module, ensure you have at least this version of the module installed for them to be able to work.
In situations where you are using a simple [IntentManaged(Mode.<mode>)]
attribute, you can use any of the following attributes:
[IntentFully]
(equivalent to[IntentManaged(Mode.Fully)]
)[IntentIgnore]
(equivalent to[IntentManaged(Mode.Ignore)]
)[IntentMerge]
(equivalent to[IntentManaged(Mode.Merge)]
)
Additionally, the following attributes can be used to override particular code management properties:
[IntentFullyAttributes]
[IntentFullyBody]
[IntentFullyComments]
[IntentFullySignature]
[IntentIgnoreAttributes]
[IntentIgnoreBody]
[IntentIgnoreComments]
[IntentIgnoreSignature]
[IntentMergeAttributes]
[IntentMergeBody]
[IntentMergeComments]
[IntentMergeSignature]
These attributes can also be combined, for example to have a method have its signature fully managed, but the rest of it ignored, you can combine them as follows:
[IntentIgnore]
[IntentFullySignature]
public void ChangeCountry(string country)
{
throw new NotImplementedException();
}
You can also combine the attributes into a single list, the following code is functionally identical to the previous example:
[IntentIgnore, IntentFullySignature]
public void ChangeCountry(string country)
{
throw new NotImplementedException();
}
"Tag Mode" attributes
Note
File level "Tag Mode" attributes were introduced in version 4.2.0
of the Intent.OutputManager.RoslynWeaver
module, ensure you have at least this version of the module installed for them to be able to work.
These attributes manage the "Tag Mode" for the current file.
Explicit Tag Mode
[assembly: IntentTagMode(TagMode.Explicit)]
or
[assembly: IntentTagModeExplicit]
The RoslynWeaver will only look at the existing file for code management attributes except in the case where the generated template output will add new syntax nodes to the existing file.
Implicit Tag Mode
[assembly: IntentTagMode(TagMode.Implicit)]
or
[assembly: IntentTagModeImplicit]
When a syntax node has no code management attribute of its own, the RoslynWeaver will attempt to find the corresponding syntax node in the template generated content and use its code management attribute instructions. If the RoslynWeaver sees that the existing file's syntax node's code management attribute instructions are identical to that of the syntax node in the generated template output, it will remove it from existing file. This mode is useful if you want to keep the amount of code management attributes in your files to an absolute minimum.
Tip
If you would like to make "implicit tag mode" the default for all files, this can be done with the tag mode application setting.
Block statement code management behaviour
Statements within code block syntax nodes (e.g. method body, constructor body, delegate body, etc) can also support certain code management capabilities.
Note
Support for management of statements was added in version 4.0.0
of the Intent.OutputManager.RoslynWeaver
module, ensure you have at least this version of the module installed for them to be able to work.
Fully mode
You can add your own statements to a code block by adding a comment above with a code management instruction, for example:
// Template generated content:
[IntentManaged(Mode.Fully)]
public void Method()
{
var variable1 = "variable1";
}
// Content in your file, added after initial generation:
[IntentManaged(Mode.Fully)]
public void Method()
{
var variable1 = "variable1";
// IntentIgnore
var myVariable = "myVariable";
}
With the above, even though the body of the method is "fully" managed, the var myVariable = "myVariable";
will not be removed due to having // IntentIgnore
above it.
This also works for statements which have statement blocks, with the above example you could have alternatively added an if
statement:
[IntentManaged(Mode.Fully)]
public void Method()
{
var variable1 = "variable1";
// IntentIgnore
if (SomeCondition)
{
var myStatement1 = "myStatement1";
var myStatement2 = "myStatement2";
}
}
Merge mode
Inline with merge behaviour of other syntax nodes, the RoslynWeaver will add any statements which are on the template and missing from your file, it will remove any statements which are no longer being generated by the template and it will always leave code manually added by a user:
// Template generated content:
[IntentManaged(Mode.Fully)]
public void Method()
{
if (_flag)
{
// Do something
}
var variable1 = "variable1";
}
// Content in your file (will not be changed by the software factory):
[IntentManaged(Mode.Fully)]
public void Method()
{
if (_flag)
{
// Do something
}
var variable1 = "variable1";
var myVariable = "myVariable";
}
If at a later time, the template content changes to add and remove statements, then previously generated statements will be removed and new ones will be added, all without touching the statements which were manually added:
// Template generated content:
[IntentManaged(Mode.Fully)]
public void Method()
{
var variable1 = "variable1";
for (var i = 1; i < 10; i++)
{
_counter++;
}
}
// Content in your file before running the software factory:
[IntentManaged(Mode.Fully)]
public void Method()
{
if (_flag)
{
// Do something
}
var variable1 = "variable1";
var myVariable = "myVariable";
}
// Content in your file after running the software factory:
[IntentManaged(Mode.Fully)]
public void Method()
{
var variable1 = "variable1";
for (var i = 1; i < 10; i++)
{
_counter++;
}
var myVariable = "myVariable";
}
Note
Prior to version 4.7.0 of the Intent.OutputManager.RoslynWeaver
module, it was not possible for it to identify what was previously generated by Intent so it would never delete anything when in merge mode.
Updating variable values
Block statement merge mode also allows you to update variable values:
// Template generated content:
[IntentManaged(Mode.Fully)]
public void Method()
{
var variable1 = "variable1";
}
// Content in your file (will not be updated by the software factory):
[IntentManaged(Mode.Fully)]
public void Method()
{
var variable1 = "my alternative value";
}
Updating other kinds statements
For other kinds of statements that you would like to update, you can use the // [IntentFully(Match = "…")]
or // [IntentIgnore(Match = "…")]
comments to specify how Intent Architect should know which statement to replace:
// Template generated content:
[IntentManaged(Mode.Merge)]
public void Method()
{
SomeOtherMethod(argument);
}
// Content in your file (will not be updated by the software factory):
[IntentManaged(Mode.Merge)]
public void Method()
{
// [IntentIgnore(Match = "SomeOtherMethod")]
SomeOtherMethod(argument, additionalArgument);
}
In the above example, the SomeOtherMethod
value for the commented out attribute lets Intent know to correlate this statement in your existing file with the first statement in the template output starting with that string.
Template authors can also add this line to their templates which lets Intent know how to try correlate the template expression with that in the existing file. When // [IntentFully(Match = "…")]
only exists on the template, it is not included in the template output.
Nested block statements
As with fully mode, if your statement was an if statement with a block statement, that would be retained too.
Module Settings
The Intent.OutputManager.RoslynWeaver
Module has the following settings which will instruct Intent Architect to behave in certain ways:
Usings Sorting
This will instruct Intent Architect to order the using directives
located above or within a namespace
scope within a C# file.
Option | Description |
---|---|
None | The order of the using directives will remain unchanged. |
Alphabetical | The using directives will be sorted alphabetically in ascending order. |
Alphabetical, place 'System' directives first | The using directives will be sorted alphabetically except it will give first priority to System based using directives. |
Tag Mode
Tag Mode can be used to control inclusion of [IntentManaged(…)]
and [DefaultIntentManaged(…)]
attributes in the output file.
Option | Description |
---|---|
Explicit | The RoslynWeaver will only look at the existing file for [IntentManaged(…)] attributes except in the case where the generated template output will add new syntax nodes to the existing file. |
Implicit | When a syntax node has no [IntentManaged(…)] attribute of its own, the RoslynWeaver will attempt to find the corresponding syntax node in the template generated content and use its [IntentManaged(…)] attribute instructions. If the RoslynWeaver sees that the existing file's syntax node's [IntentManaged(…)] is identical to that of the syntax node in the generated template output, it will remove it from existing file. This mode is useful if you want to keep the amount of [IntentManaged(…)] attributes in your files to an absolute minimum. |
Template Only | Like with Implicit mode, when a syntax node has no [IntentManaged(…)] attribute of its own, the RoslynWeaver will attempt to find the corresponding syntax node in the template generated content and use its [IntentManaged(…)] attribute instructions. After running the code management logic, RoslynWeaver will remove all [IntentManaged(…)] and [DefaultIntentManaged(…)] attributes from the file. This mode is useful if you want absolutely no [IntentManaged(…)] or [IntentDefaultManaged(…)] attributes in your code base. WARNING: This mode removes all code management instructions from your code files, ensure you have a backup of your files (or have a commit in your source control management) in case you wish to rollback this change. |
Usings Placement
This will instruct Intent Architect where to place the using
directives within a C# file.
Option | Description |
---|---|
Default | All using directives will be placed at the top of the C# file. |
Move to inside namespace | All using directives will be placed within the scope of a namespace . |
Frequently asked questions
How can I disable the RoslynWeaver from formatting my C# files?
By default the RoslynWeaver will automatically format files under code management. If this is undesired you can disable this behaviour by using the .WithAutoFormatting(...)
extension method with the first parameter set to false
in the DefineFileConfig
method of your template:
protected override CSharpFileConfig DefineFileConfig()
{
return new CSharpFileConfig(
className: "MyClass",
@namespace: OutputTarget.GetNamespace())
.WithAutoFormatting(false);
}
Why isn't the RoslynWeaver removing extraneous using directives
?
By default RoslynWeaver uses Merge
as the management mode for using directives
, where it will never remove nodes unless they were previously generated by the software factory but aren't any more. This can be overridden by adding an [assembly: DefaultIntentManaged(Mode.Fully, Targets = Targets.Usings)]
in your file, typically just beneath the existing [DefaultIntentManaged(…)]
attribute, for example:
[assembly: DefaultIntentManaged(Mode.Fully)]
[assembly: DefaultIntentManaged(Mode.Fully, Targets = Targets.Usings)]
Why are are class members (such as methods, properties, fields, etc) being overwritten by Intent even though the class has [IntentManaged(Mode.Merge)]
/ [IntentManaged(Mode.Fully, Body = Mode.Merge)]
on it?
Generally, the IntentManaged
attribute does not effect descendant syntax nodes (an exception being [IntentManaged(Mode.Ignore)]
), it only applies to the syntax node it's directly applied to (the class in this case), allowing you to "opt-out" of being fully managed.
When you do not specify the Body
mode specifically for an IntentManaged
attribute, then the all its properties (including Body
) use the default constructor's mode. For type declarations (e.g. class, interface, enum, record, etc), the Body
mode is used to control whether or not members (i.e. methods, fields, properties, etc) are added/removed, this is what Intent Architect will do under the following Body
modes:
- Fully: Members are added to/removed from the existing file to ensure they match the template output.
- Merge: Any members from the template output which are missing from the existing file will be added, additional members which were previously added by Intent will be removed and any additional members which where manually user added will not be removed.
- Ignore: No members will be added to or removed from the existing file.
How does Intent know what was previously generated and is now no longer being generated?
After pressing Apply in the software factory, for all files managed by the software factory (except for files which are unchecked in the Changes view), the Software Factory will save the template output (i.e. the "pre-merge" output) for each file in the .intent/previous_output
of the current Intent Architect application.
Why didn't Intent automatically delete something which was no longer being generated even though it was in merge mode?
The .intent
folder which contains the previous template output is not committed to source control so as to prevent these files causing noise in your SCM logs and possible pull requests. This means that if you have a fresh checkout of your source code, or for some reason you haven't applied any software changes on your machine prior to making an update which should delete something, the previous output may be missing or out of date (for example to due it last being run by a different user on a different machine), so it may not be aware of some things which were actually generated by Intent.