Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<IsPackable>True</IsPackable>
<Nullable>enable</Nullable>
<PackageId>DependencyModules.SourceGenerator.Impl</PackageId>
<Description>The source code of the DependencyModules generator, for the source generator of a framework. Set PackageDependencyModuleIncludeSource to true to compile the source code into your generator. The source code also uses the CSharpAuthor package. Add CSharpAuthor 2.0.0 and set PackageCSharpAuthorIncludeSource to true. For usual projects, use DependencyModules.SourceGenerator.</Description>
<Description>The source code of the DependencyModules generator, for the source generator of a framework. Set PackageDependencyModuleIncludeSource to true to compile the source code into your generator. The package also contains the source code of CSharpAuthor, which the generator source code uses. For usual projects, use DependencyModules.SourceGenerator.</Description>
<DevelopmentDependency>true</DevelopmentDependency>
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- Source-only package: ships .cs under src/ and no lib/, which is exactly what NU5128 flags. -->
Expand Down
2 changes: 1 addition & 1 deletion website/guide/aot.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The generator does not use `private` constructors. It gets each constructor para

The generator does not write factories for generic classes. It also does not write factories for the service types that a class with `[Intercept]` registers.

If the generator writes factories, `[Decorator(Implementation = ...)]` cannot find the implementation. The generator then gives the warning DM0022. For more information, refer to [Decorate one implementation](./decorators.md#decorate-one-implementation).
Each factory that the generator writes returns its class. Thus a decorator that sets `Implementation` finds the implementation of the registration. For more information, refer to [Decorate one implementation](./decorators.md#decorate-one-implementation).

## Test packages

Expand Down
28 changes: 12 additions & 16 deletions website/guide/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ The generator reads the `Conventions` method when you compile. The method does n

If the generator cannot read a statement, it gives the error DM0009 and does not use the statement.

For `WithName`, `WithoutName`, and the environment calls, the generator does not always find an argument that is not a compile-time value. If the call has a different argument that is a compile-time value, the generator ignores the argument that is not a compile-time value. It gives no diagnostic. For example, the generator reads `IfEnvironmentValue(Keys.Feature, "on")` as `IfEnvironmentValue("on")` if `Keys.Feature` is a `static readonly` field. Use string literals or `const` fields for these arguments.
If one argument of a call is not a value that the compiler knows, the generator gives DM0009 for the statement. For example, `IfEnvironmentValue(Keys.Feature, "on")` gives DM0009 if `Keys.Feature` is a `static readonly` field. Use string literals or `const` fields for these arguments.

You can implement the method as a public method or as an explicit interface implementation: `void IConventionModule.Conventions(IConventionDefinitions conventions)`. If a type has the two methods, the generator reads the explicit interface implementation.

Expand Down Expand Up @@ -84,14 +84,14 @@ A convention examines only the classes of the project that contains the module.

- It is a class, a record class, or a record struct.
- It is not `static` and not `abstract`.
- It is not `private` and not `protected`. An `internal` class is a candidate.
- The generated code can use it. Thus it is not `private`, `protected`, or `file`, and it is not in a `private` or `protected` class. An `internal` class and a `protected internal` class are candidates.
- It does not have a service attribute or `[Decorator]`.

A class with a service attribute keeps the registration from its attribute. The convention does not register this class again.

A nested class can be a candidate. Write an access modifier on a nested class. A nested class without an access modifier is private, but the generator examines only the modifiers that you write. Thus the convention selects this class, and the generated code does not compile.
A nested class can be a candidate. A nested class without an access modifier is `private`. Thus the convention does not select it.

A selected class must have a `public` constructor, or no declared constructor. The service provider uses only `public` constructors. If the class has only `private` or `protected` constructors, the generator gives the warning DM0006 and does not register the class. If the class has only `internal` constructors, the generator registers the class and gives no diagnostic. The service provider then cannot make the service, unless the module uses [generated factories](./aot.md#generated-factories).
A selected class must have a `public` constructor, or no declared constructor. The service provider uses only `public` constructors. If the module uses [generated factories](./aot.md#generated-factories), the generated code calls the constructor. Then an `internal` or `protected internal` constructor is also correct. This is not true for a class with `[Intercept]`, because the generator does not write a factory for it. If the class has no constructor that its registration can use, the generator gives the warning DM0006 and does not register the class.

## Lifetime

Expand Down Expand Up @@ -120,9 +120,7 @@ For `AsSelfWithInterfaces()`, the interfaces of the class are the interfaces in

If a convention calls `AsSelfWithInterfaces()` or `AlsoAsSelf()`, the interface registrations get the instance from the registration of the class type. Thus, for the `Singleton` and `Scoped` lifetimes, all these registrations give the same instance in a scope. If `AsSelfWithInterfaces()` finds no interface, it registers only the class type.

::: warning
Do not use `AlsoAsSelf()` or `AsSelfWithInterfaces()` for generic classes or with `WithKey`. In these conditions, the generated code does not compile.
:::
The generator cannot cross-wire a generic class. If a convention with `AlsoAsSelf()` or `AsSelfWithInterfaces()` selects a generic class, the generator gives the warning DM0014 and does not register that class. Select the generic classes with a different convention that does not use these calls.

Use only one of `AsSelf()`, `AsSelfWithInterfaces()`, and `AlsoAsSelf()` in a convention. If you use more than one, the generator gives the error DM0009.

Expand Down Expand Up @@ -280,19 +278,15 @@ In a referenced assembly, a class is a candidate when all these conditions are t

A selected class must have a `public` constructor.

The generator ignores the environment attributes of a class from a referenced assembly. The conditions of the convention are applicable.
The generator reads the environment attributes of a class from a referenced assembly. The conditions of the class and the conditions of the convention are applicable. If a condition of the class has no name or no key, the generator gives the warning DM0012 at the convention statement.

A class from a referenced assembly has no location in your source code. Thus the generator shows its diagnostics, for example DM0010, at the convention statement.

## Keys and registration type

`WithKey(key)` registers each class as a keyed service. The generator writes the key into the generated code without changes.
`WithKey(key)` registers each class as a keyed service. The generator writes the key into the generated code without changes. With `AlsoAsSelf()` or `AsSelfWithInterfaces()`, all registrations of a class use the key.

::: warning
Do not use `WithKey` with `AlsoAsSelf()` or `AsSelfWithInterfaces()`. The generated code does not compile.
:::

`Using(RegistrationType.Try)` sets the registration type. For the values, refer to [Registration type](./services.md#registration-type). Write the argument as `RegistrationType.Try`. If you write the full name of the enum or use a constant, the generator uses `Add` and gives no diagnostic.
`Using(RegistrationType.Try)` sets the registration type. For the values, refer to [Registration type](./services.md#registration-type). The generator reads the value of the argument. Thus you can also write the full name of the enum member or use a constant. If the argument is not a value that the compiler knows, the generator gives the error DM0009.

```csharp
conventions.RegisterAll<IStore>().WithKey("archive").Using(RegistrationType.Try).AsScoped();
Expand All @@ -311,17 +305,19 @@ A convention can register its classes only in some environments. Use these calls
| `IfNotEnvironmentValue("FEATURE_X")` | The environment has no value for the key. |
| `IfNotEnvironmentValue("FEATURE_X", "on")` | The value for the key is not equal to the given value. |

If a class in the project has environment attributes, for example `[IfEnvironment("Development")]`, the conditions of the class are also applicable. All conditions must be true. For more information, refer to [Environments](./environments.md).
If a selected class has environment attributes, for example `[IfEnvironment("Development")]`, the conditions of the class are also applicable. This is also true for a class from a referenced assembly. All conditions must be true. For more information, refer to [Environments](./environments.md).

## Diagnostics

| ID | Severity | Cause |
| --- | --- | --- |
| DM0004 | Error | Two conventions in one module register the same class as the same service type. The generator does not write these two registrations. |
| DM0005 | Warning | A convention selects no classes. |
| DM0006 | Warning | A selected class has no constructor that the generated code can use. |
| DM0006 | Warning | A selected class has no constructor that its registration can use. |
| DM0009 | Error | The generator cannot read a convention statement. |
| DM0010 | Info | The generator registered a class from a convention. The message shows the service type and the module. |
| DM0012 | Warning | An environment condition of a selected class has no name or no key. |
| DM0014 | Warning | A convention with `AlsoAsSelf()` or `AsSelfWithInterfaces()` selects a generic class. |

If two conventions select one class for two different service types, this is not an error. The class then gets two registrations. For example, a class that implements `IFirstRole` and `ISecondRole` can have a singleton registration as `IFirstRole` and a scoped registration as `ISecondRole`.

Expand Down
14 changes: 7 additions & 7 deletions website/guide/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ public class AuditedOrderService(IOrderService inner, IAuditLog log) : IOrderSer

When you get `IOrderService`, the service provider gives `AuditedOrderService`. `AuditedOrderService` gets `OrderService` in the `inner` parameter.

The generator finds the service type from the constructor. The service type is the first constructor parameter with a type that the class declaration of the decorator contains. The generator does not examine the interfaces of base classes or the interfaces that an interface derives from. If the generator finds no service type, it ignores the decorator and gives no diagnostic. The `Service` property can also set the service type, for example `[Decorator(Service = typeof(IOrderService))]`.
The generator finds the service type from the constructor. The service type is the first constructor parameter with a type that the decorator implements. The generator examines the base classes of the decorator and all its interfaces, also the interfaces that an interface derives from. If the generator finds no service type, it gives the warning DM0025 and does not apply the decorator. The `Service` property can also set the service type, for example `[Decorator(Service = typeof(IOrderService))]`.

The generator does not register the decorator class as a service. The service provider gives the other constructor parameters, for example `IAuditLog` in the example.

Give the decorator a `public` constructor. The generated code calls this constructor.
The generated code calls the constructor of the decorator. Thus the constructor must be `public`, `internal`, or `protected internal`. If the decorator has no such constructor, the generator gives the warning DM0025 and does not apply the decorator.

## Which registrations a decorator changes

Expand Down Expand Up @@ -101,7 +101,7 @@ In this example, `Describe()` gives `outer(inner(price))`.

The decorators of all modules and the interceptors are in one sequence. The default `Order` value is 0. The source code recommends values from 0 to 999 for packages and values of 1000 and more for application code. Then the decorators of the application are the outer decorators. The generator does not examine these ranges.

Write `Order` as a number, for example `Order = 1000`. The generator reads the text of the value. If you use a constant or `1_000`, the `Order` value is 0, and the generator gives no diagnostic. `[Decorate]` also accepts a constant.
The generator reads the value of `Order`, not its text. Thus you can also use a constant or digit separators, for example `Order = 1_000`.

If two decorators in one module have the same service type and the same `Order` value, the generator gives the error DM0007.

Expand Down Expand Up @@ -159,7 +159,7 @@ The generator makes a closed decorator for each closed registration of the servi

These conditions are applicable to a generic decorator:

- The decorator must have the same type parameters as the service type, in the same sequence.
- The decorator must have the same type parameters as the service type, in the same sequence. If it does not, the generator gives the warning DM0025.
- The generator uses only the closed service types that the same project registers.
- If a closed service type does not agree with the constraints of the decorator, the generator does not use the decorator for that type.

Expand Down Expand Up @@ -225,15 +225,15 @@ public class CardPaymentCheck(IPaymentMethod inner) : IPaymentMethod

`CardPaymentCheck` decorates only the registration of `CardPayment`.

`Implementation` has an effect only on type registrations. In an instance registration or a factory registration, the decorator does not know the implementation type. Thus the decorator changes the registration.
The decorator finds the implementation of a registration from its type, from its instance, or from the return type of its factory. If a factory returns `object` or the service type, the registration does not show its implementation. The decorator does not change that registration.

If a module writes [generated factories](./aot.md#generated-factories), its registrations are factory registrations. The generator then gives the warning DM0022, because the decorator changes all registrations of the service type. If `[DependencyModule]` sets `GenerateFactories`, the generator uses this value for the module. It does not use the `DependencyModules_GenerateFactories` MSBuild property.
The factories that the generator writes return their class. Thus `Implementation` also operates in a module that uses [generated factories](./aot.md#generated-factories).

## Environment conditions

A `[Decorator]` class can have environment attributes, for example `[IfEnvironment("Development")]`. The decorator then changes the registrations only when the conditions are true. If the conditions are false, the other decorators keep their positions in the sequence. For the attributes, refer to [Environments](./environments.md).

The generator ignores the environment attributes of a decorator that `[Decorate]` adds.
The generator also reads the environment attributes of a decorator that `[Decorate]` adds. If a condition has no name or no key, the generator gives the warning DM0012.

## Realms

Expand Down
6 changes: 3 additions & 3 deletions website/guide/environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ The generator writes an `if` statement around the registrations of the class. Th

## Conditions on decorators and conventions

You can put the same attributes on a `[Decorator]` class. The decorator then changes the registrations only when the conditions are true. The generator ignores these attributes on a decorator that `[Decorate]` adds. For more information, refer to [Decorators](./decorators.md#environment-conditions).
You can put the same attributes on a `[Decorator]` class. The decorator then changes the registrations only when the conditions are true. The generator also reads these attributes on a decorator that `[Decorate]` adds. For more information, refer to [Decorators](./decorators.md#environment-conditions).

A convention can also have conditions. For more information, refer to [Conventions](./conventions.md#environment-conditions).
A convention can also have conditions. The conditions of a class that a convention selects are also applicable, also for a class from a referenced assembly. For more information, refer to [Conventions](./conventions.md#environment-conditions).

## Read the environment in a module

Expand Down Expand Up @@ -175,7 +175,7 @@ public partial class MailOptionsModule : IEnvironmentServiceCollectionConfigurat
| DM0011 | Info | A service has conditions. The message shows the conditions. |
| DM0012 | Warning | A condition has no environment name or no key. The generator ignores this condition. |

The generator gives these diagnostics only for classes with a service attribute. A condition attribute can have no name or no key on a `[Decorator]` class or on a class that a convention selects. The generator then ignores the condition and gives no diagnostic. On a convention statement, a condition call without a name or a key gives the error DM0009.
The generator gives DM0011 only for classes with a service attribute. It gives DM0012 also for a `[Decorator]` class, for a decorator that `[Decorate]` adds, and for a class that a convention selects. For a decorator that `[Decorate]` adds, DM0012 is at the module. For a class from a referenced assembly, DM0012 is at the convention statement. On a convention statement, a condition call without a name or a key gives the error DM0009.

## Environments in tests

Expand Down
Loading